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

package cpu

import (
	"fmt"
	"io"
	"log"
	"net"
	"os"
	"strconv"
	"strings"
	"time"

	// We use this ssh because it implements port redirection.
	// It can not, however, unpack password-protected keys yet.
	// TODO: get rid of krpty
	// We use this ssh because it can unpack password-protected private keys.

	"golang.org/x/crypto/ssh"
)

const (
	// From setting up the forward to having the nonce written back to us,
	// we would like to default to 100ms. This is a lot, considering that at this point,
	// the sshd has forked a server for us and it's waiting to be
	// told what to do.
	defaultTimeOut = time.Duration(100 * time.Millisecond)
	defaultPort    = "23"
)

// V allows debug printing.
var V = func(string, ...interface{}) {}

// Cmd is a cpu client.
// It implements as much of exec.Command as makes sense.
type Cmd struct {
	config  ssh.ClientConfig
	client  *ssh.Client
	session *ssh.Session
	// CPU-specific options.
	// As in exec.Command, these controls are exposed and can
	// be set directly.
	Host string
	// HostName as found in .ssh/config; set to Host if not found
	HostName       string
	Args           []string
	Root           string
	HostKeyFile    string
	PrivateKeyFile string
	Port           string
	Timeout        time.Duration
	Env            []string
	Stdin          io.WriteCloser
	Stdout         io.Reader
	Stderr         io.Reader

	nonce   nonce
	network string // This is a variable but we expect it will always be tcp
	port9p  uint16 // port on which we serve 9p
	cmd     string // The command is built up, bit by bit, as we configure the client
}

// Command implements exec.Command. The required parameter is a host.
// The args arg args to $SHELL. If there are no args, then starting $SHELL
// is assumed.
func Command(host string, args ...string) *Cmd {
	if len(args) == 0 {
		args = []string{os.Getenv("SHELL")}
	}

	return &Cmd{
		Host:     host,
		HostName: GetHostName(host),
		Args:     args,
		Port:     defaultPort,
		Timeout:  defaultTimeOut,
		config: ssh.ClientConfig{
			User:            os.Getenv("USER"),
			HostKeyCallback: ssh.InsecureIgnoreHostKey(),
		},
		network: "tcp",
		// Safety first: if they want a namespace, they must say so
		Root: "",
		// The command, always, at least, starts with "cpu"
		cmd: "cpud -remote -bin cpud",
	}
}

// WithPrivateKeyFile adds a private key file to a Cmd
func (c *Cmd) WithPrivateKeyFile(key string) *Cmd {
	c.PrivateKeyFile = key
	return c
}

// WithHostKeyFile adds a host key to a Cmd
func (c *Cmd) WithHostKeyFile(key string) *Cmd {
	c.HostKeyFile = key
	return c
}

// WithPort adds a port to a Cmd
func (c *Cmd) WithPort(port string) *Cmd {
	c.Port = port
	return c
}

// WithRoot adds a root to a Cmd
func (c *Cmd) WithRoot(root string) *Cmd {
	c.Root = root
	return c
}

// SetPort sets the port in the Cmd.
// It calls GetPort with the passed-in port
// before assigning it.
func (c *Cmd) SetPort(port string) error {
	c.Port = port
	p, err := GetPort(c.HostName, c.Port)
	if err == nil {
		c.Port = p
	}
	return err
}

// Dial implements ssh.Dial for cpu.
// Additionaly, if Cmd.Root is not "", it
// starts up a server for 9p requests.
func (c *Cmd) Dial() error {
	if err := c.UserKeyConfig(); err != nil {
		return err
	}
	addr := net.JoinHostPort(c.HostName, c.Port)
	cl, err := ssh.Dial(c.network, addr, &c.config)
	V("cpu:ssh.Dial(%s, %s, %v): (%v, %v)", c.network, addr, c.config, cl, err)
	if err != nil {
		return fmt.Errorf("Failed to dial: %v", err)
	}

	c.client = cl
	// Specifying a root is required for a remote namespace.
	if len(c.Root) == 0 {
		return nil
	}
	// If the namespace is empty, a nameserver won't be started
	if _, ok := os.LookupEnv("CPU_NAMESPACE"); !ok {
		return fmt.Errorf("Root is set to %q but there is no CPU_NAMESPACE", c.Root)
	}

	// Arrange port forwarding from remote ssh to our server.
	// Request the remote side to open port 5640 on all interfaces.
	// Note: cl.Listen returns a TCP listener with network is "tcp"
	// or variants. This lets us use a listen deadline.
	l, err := cl.Listen("tcp", "127.0.0.1:0")
	if err != nil {
		return fmt.Errorf("cpu client listen for forwarded 9p port %v", err)
	}
	V("ssh.listener %v", l.Addr().String())
	ap := strings.Split(l.Addr().String(), ":")
	if len(ap) == 0 {
		return fmt.Errorf("Can't find a port number in %v", l.Addr().String())
	}
	port9p, err := strconv.ParseUint(ap[len(ap)-1], 0, 16)
	if err != nil {
		return fmt.Errorf("Can't find a 16-bit port number in %v", l.Addr().String())
	}
	c.port9p = uint16(port9p)

	V("listener %T %v addr %v port %v", l, l, l.Addr().String(), port9p)

	nonce, err := generateNonce()
	if err != nil {
		log.Fatalf("Getting nonce: %v", err)
	}
	c.Env = append(c.Env, "CPUNONCE="+nonce.String())
	c.nonce = nonce
	go c.srv(l)
	return nil
}

// Start implements exec.Start for CPU.
func (c *Cmd) Start() error {
	var err error
	if c.client == nil {
		return fmt.Errorf("Cmd has no client")
	}
	if c.session, err = c.client.NewSession(); err != nil {
		return err
	}

	c.Env = append(c.Env, "CPUNONCE="+c.nonce.String())
	if err := c.SetEnv(c.Env...); err != nil {
		return err
	}
	if c.Stdin, err = c.session.StdinPipe(); err != nil {
		return err
	}
	if c.Stdout, err = c.session.StdoutPipe(); err != nil {
		return err
	}
	if c.Stderr, err = c.session.StderrPipe(); err != nil {
		return err
	}

	// Unlike the cpu command source, which assumes an SSH-like stdin, SSHStdin in this package will need to be set up explicitly.
	//go c.SSHStdin(i, c.Stdin)
	// N.B.: if a server was needed, it was
	// started in Dial.

	// assemble the command.
	cmd := fmt.Sprintf("%s -port9p %v %q", c.cmd, c.port9p, strings.Join(c.Args, " "))
	V("call session.Start(%s)", cmd)
	if err := c.session.Start(cmd); err != nil {
		return fmt.Errorf("Failed to run %v: %v", c, err.Error())
	}
	return nil
}

// Wait waits for a Cmd to finish.
func (c *Cmd) Wait() error {
	return c.session.Wait()
}

// Run runs a command with Start, and waits for it to finish with Wait.
func (c *Cmd) Run() error {
	if err := c.Start(); err != nil {
		return err
	}
	return c.Wait()
}

// Close ends a cpu session, doing whatever is needed.
func (c *Cmd) Close() (errs []error) {
	if c.session != nil {
		if err := c.session.Close(); err != nil && err != io.EOF {
			errs = append(errs, fmt.Errorf("Closing session: got %v, want nil", err))
		}
	}
	return errs
}
<commit_msg>Add namespace setup code to the package<commit_after>// Copyright 2018-2019 the u-root Authors. All rights reserved
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package cpu

import (
	"fmt"
	"io"
	"log"
	"net"
	"os"
	"strconv"
	"strings"
	"time"

	// We use this ssh because it implements port redirection.
	// It can not, however, unpack password-protected keys yet.
	// TODO: get rid of krpty
	// We use this ssh because it can unpack password-protected private keys.

	"golang.org/x/crypto/ssh"
)

const (
	// From setting up the forward to having the nonce written back to us,
	// we would like to default to 100ms. This is a lot, considering that at this point,
	// the sshd has forked a server for us and it's waiting to be
	// told what to do.
	defaultTimeOut   = time.Duration(100 * time.Millisecond)
	defaultPort      = "23"
	DefaultNameSpace = "/lib:/lib64:/usr:/bin:/etc:/home"
)

// V allows debug printing.
var V = func(string, ...interface{}) {}

// Cmd is a cpu client.
// It implements as much of exec.Command as makes sense.
type Cmd struct {
	config  ssh.ClientConfig
	client  *ssh.Client
	session *ssh.Session
	// CPU-specific options.
	// As in exec.Command, these controls are exposed and can
	// be set directly.
	Host string
	// HostName as found in .ssh/config; set to Host if not found
	HostName       string
	Args           []string
	Root           string
	HostKeyFile    string
	PrivateKeyFile string
	Port           string
	Timeout        time.Duration
	Env            []string
	Stdin          io.WriteCloser
	Stdout         io.Reader
	Stderr         io.Reader
	// NameSpace is a string as defined in the cpu documentation.
	NameSpace string

	nonce   nonce
	network string // This is a variable but we expect it will always be tcp
	port9p  uint16 // port on which we serve 9p
	cmd     string // The command is built up, bit by bit, as we configure the client
}

// Command implements exec.Command. The required parameter is a host.
// The args arg args to $SHELL. If there are no args, then starting $SHELL
// is assumed.
func Command(host string, args ...string) *Cmd {
	if len(args) == 0 {
		args = []string{os.Getenv("SHELL")}
	}

	return &Cmd{
		Host:     host,
		HostName: GetHostName(host),
		Args:     args,
		Port:     defaultPort,
		Timeout:  defaultTimeOut,
		config: ssh.ClientConfig{
			User:            os.Getenv("USER"),
			HostKeyCallback: ssh.InsecureIgnoreHostKey(),
		},
		network: "tcp",
		// Safety first: if they want a namespace, they must say so
		Root: "",
		// The command, always, at least, starts with "cpu"
		cmd: "cpud -remote -bin cpud",
	}
}

// WithNameSpace adds a namespace to Cmd. There is no default: having some default
// violates the principle of least surprise for package users.
// The word "none" is reserved to mean the package will not set the
// CPU_NAMESPACE environment variable.
func (c *Cmd) WithNameSpace(ns string) *Cmd {
	c.NameSpace = ns
	return c
}

// WithPrivateKeyFile adds a private key file to a Cmd
func (c *Cmd) WithPrivateKeyFile(key string) *Cmd {
	c.PrivateKeyFile = key
	return c
}

// WithHostKeyFile adds a host key to a Cmd
func (c *Cmd) WithHostKeyFile(key string) *Cmd {
	c.HostKeyFile = key
	return c
}

// WithPort adds a port to a Cmd
func (c *Cmd) WithPort(port string) *Cmd {
	c.Port = port
	return c
}

// WithRoot adds a root to a Cmd
func (c *Cmd) WithRoot(root string) *Cmd {
	c.Root = root
	return c
}

// SetPort sets the port in the Cmd.
// It calls GetPort with the passed-in port
// before assigning it.
func (c *Cmd) SetPort(port string) error {
	c.Port = port
	p, err := GetPort(c.HostName, c.Port)
	if err == nil {
		c.Port = p
	}
	return err
}

// Dial implements ssh.Dial for cpu.
// Additionaly, if Cmd.Root is not "", it
// starts up a server for 9p requests.
func (c *Cmd) Dial() error {
	if err := c.UserKeyConfig(); err != nil {
		return err
	}
	addr := net.JoinHostPort(c.HostName, c.Port)
	cl, err := ssh.Dial(c.network, addr, &c.config)
	V("cpu:ssh.Dial(%s, %s, %v): (%v, %v)", c.network, addr, c.config, cl, err)
	if err != nil {
		return fmt.Errorf("Failed to dial: %v", err)
	}

	c.client = cl
	// Specifying a root is required for a remote namespace.
	if len(c.Root) == 0 {
		return nil
	}

	// Arrange port forwarding from remote ssh to our server.
	// Request the remote side to open port 5640 on all interfaces.
	// Note: cl.Listen returns a TCP listener with network is "tcp"
	// or variants. This lets us use a listen deadline.
	l, err := cl.Listen("tcp", "127.0.0.1:0")
	if err != nil {
		return fmt.Errorf("cpu client listen for forwarded 9p port %v", err)
	}
	V("ssh.listener %v", l.Addr().String())
	ap := strings.Split(l.Addr().String(), ":")
	if len(ap) == 0 {
		return fmt.Errorf("Can't find a port number in %v", l.Addr().String())
	}
	port9p, err := strconv.ParseUint(ap[len(ap)-1], 0, 16)
	if err != nil {
		return fmt.Errorf("Can't find a 16-bit port number in %v", l.Addr().String())
	}
	c.port9p = uint16(port9p)

	V("listener %T %v addr %v port %v", l, l, l.Addr().String(), port9p)

	nonce, err := generateNonce()
	if err != nil {
		log.Fatalf("Getting nonce: %v", err)
	}
	c.Env = append(c.Env, "CPUNONCE="+nonce.String())
	if c.NameSpace != "none" {
		c.Env = append(c.Env, "CPU_NAMESPACE="+c.NameSpace)
	}
	c.nonce = nonce
	go c.srv(l)
	return nil
}

// Start implements exec.Start for CPU.
func (c *Cmd) Start() error {
	var err error
	if c.client == nil {
		return fmt.Errorf("Cmd has no client")
	}
	if c.session, err = c.client.NewSession(); err != nil {
		return err
	}

	c.Env = append(c.Env, "CPUNONCE="+c.nonce.String())
	if err := c.SetEnv(c.Env...); err != nil {
		return err
	}
	if c.Stdin, err = c.session.StdinPipe(); err != nil {
		return err
	}
	if c.Stdout, err = c.session.StdoutPipe(); err != nil {
		return err
	}
	if c.Stderr, err = c.session.StderrPipe(); err != nil {
		return err
	}

	// Unlike the cpu command source, which assumes an SSH-like stdin, SSHStdin in this package will need to be set up explicitly.
	//go c.SSHStdin(i, c.Stdin)
	// N.B.: if a server was needed, it was
	// started in Dial.

	// assemble the command.
	cmd := fmt.Sprintf("%s -port9p %v %q", c.cmd, c.port9p, strings.Join(c.Args, " "))
	V("call session.Start(%s)", cmd)
	if err := c.session.Start(cmd); err != nil {
		return fmt.Errorf("Failed to run %v: %v", c, err.Error())
	}
	return nil
}

// Wait waits for a Cmd to finish.
func (c *Cmd) Wait() error {
	return c.session.Wait()
}

// Run runs a command with Start, and waits for it to finish with Wait.
func (c *Cmd) Run() error {
	if err := c.Start(); err != nil {
		return err
	}
	return c.Wait()
}

// Close ends a cpu session, doing whatever is needed.
func (c *Cmd) Close() (errs []error) {
	if c.session != nil {
		if err := c.session.Close(); err != nil && err != io.EOF {
			errs = append(errs, fmt.Errorf("Closing session: got %v, want nil", err))
		}
	}
	return errs
}
<|endoftext|>


<commit_before>/*
 * Copyright 2018 The Knative Authors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package v1alpha1

import (
	"encoding/json"

	"k8s.io/api/core/v1"
	meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

// +genclient
// +genclient:noStatus
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// +k8s:defaulter-gen=true

// Channel represents a named endpoint on which a Bus accepts event delivery and
// corresponds to the channels.channels.knative.dev CRD. The Bus handles
// provisioning channels, delivering events to Channels, and delivering events
// from Channels to their Subscriptions.
type Channel struct {
	meta_v1.TypeMeta   `json:",inline"`
	meta_v1.ObjectMeta `json:"metadata"`
	Spec               ChannelSpec   `json:"spec"`
	Status             ChannelStatus `json:"status,omitempty"`
}

// ChannelSpec specifies the Bus backing a channel and the configuration
// arguments for the channel.
type ChannelSpec struct {
	// Name of the bus backing this channel (optional)
	Bus string `json:"bus`

	// ClusterBus name of the clusterbus backing this channel (mutually exclusive with Bus)
	ClusterBus string `json:"clusterBus"`

	// Arguments is a list of configuration arguments for the Channel. The
	// Arguments for a channel must contain values for each of the Parameters
	// specified by the Bus' spec.parameters.Channels field except the
	// Parameters that have a default value. If a Parameter has a default value
	// and it is not in the list of Arguments, the default value will be used; a
	// Parameter without a default value that does not have an Argument will
	// result in an error setting up the Channel.
	Arguments *[]Argument `json:"arguments,omitempty"`
}

type ChannelConditionType string

const (

	// Ready is set when all other conditions are met and the channel is ready to accept traffic.
	ChannelReady ChannelConditionType = "Ready"

	// Serviceable means the service addressing the channel exists.
	ChannelServiceable ChannelConditionType = "Serviceable"

	// Routable means the virtual service forwarding traffic from the channel service to the
	// bus is created.
	ChannelRoutable ChannelConditionType = "Routeable"

	// Provisioned means the channel backing construct on the bus middleware has been set up.
	ChannelProvisioned ChannelConditionType = "Provisioned"
)

// ChannelCondition describes the state of a channel at a point in time.
type ChannelCondition struct {
	// Type of channel condition.
	Type ChannelConditionType `json:"type"`
	// Status of the condition, one of True, False, Unknown.
	Status v1.ConditionStatus `json:"status"`
	// The last time this condition was updated.
	LastUpdateTime meta_v1.Time `json:"lastUpdateTime,omitempty"`
	// Last time the condition transitioned from one status to another.
	LastTransitionTime meta_v1.Time `json:"lastTransitionTime,omitempty"`
	// The reason for the condition's last transition.
	Reason string `json:"reason,omitempty"`
	// A human readable message indicating details about the transition.
	Message string `json:"message,omitempty"`
}

// ChannelStatus (computed) for a channel
type ChannelStatus struct {
	// A reference to the k8s Service backing this channel, if successfully synced.
	Service *v1.LocalObjectReference `json:"service,omitempty"`

	// A reference to the istio VirtualService backing this channel, if successfully synced.
	VirtualService *v1.LocalObjectReference `json:"virtualService,omitempty"`

	// Represents the latest available observations of a channel's current state.
	// +patchMergeKey=type
	// +patchStrategy=merge
	Conditions []ChannelCondition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"`

	// DomainInternal holds the top-level domain that will distribute traffic
	// over the provided targets from inside the cluster. It generally has the
	// form {channel}.{namespace}.svc.cluster.local
	// +optional
	DomainInternal string `json:"domainInternal,omitempty"`
}

func (cs *ChannelStatus) GetCondition(t ChannelConditionType) *ChannelCondition {
	for _, cond := range cs.Conditions {
		if cond.Type == t {
			return &cond
		}
	}
	return nil
}

func (c *Channel) GetSpecJSON() ([]byte, error) {
	return json.Marshal(c.Spec)
}

// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object

// ChannelList returned in list operations
type ChannelList struct {
	meta_v1.TypeMeta `json:",inline"`
	meta_v1.ListMeta `json:"metadata"`
	Items            []Channel `json:"items"`
}
<commit_msg>Fix broken json tag. Mark bus&clusterBus as optional. (#204)<commit_after>/*
 * Copyright 2018 The Knative Authors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package v1alpha1

import (
	"encoding/json"

	"k8s.io/api/core/v1"
	meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

// +genclient
// +genclient:noStatus
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// +k8s:defaulter-gen=true

// Channel represents a named endpoint on which a Bus accepts event delivery and
// corresponds to the channels.channels.knative.dev CRD. The Bus handles
// provisioning channels, delivering events to Channels, and delivering events
// from Channels to their Subscriptions.
type Channel struct {
	meta_v1.TypeMeta   `json:",inline"`
	meta_v1.ObjectMeta `json:"metadata"`
	Spec               ChannelSpec   `json:"spec"`
	Status             ChannelStatus `json:"status,omitempty"`
}

// ChannelSpec specifies the Bus backing a channel and the configuration
// arguments for the channel.
type ChannelSpec struct {
	// Name of the bus backing this channel (optional)
	Bus string `json:"bus,omitempty"`

	// ClusterBus name of the clusterbus backing this channel (mutually exclusive with Bus)
	ClusterBus string `json:"clusterBus,omitempty"`

	// Arguments is a list of configuration arguments for the Channel. The
	// Arguments for a channel must contain values for each of the Parameters
	// specified by the Bus' spec.parameters.Channels field except the
	// Parameters that have a default value. If a Parameter has a default value
	// and it is not in the list of Arguments, the default value will be used; a
	// Parameter without a default value that does not have an Argument will
	// result in an error setting up the Channel.
	Arguments *[]Argument `json:"arguments,omitempty"`
}

type ChannelConditionType string

const (

	// Ready is set when all other conditions are met and the channel is ready to accept traffic.
	ChannelReady ChannelConditionType = "Ready"

	// Serviceable means the service addressing the channel exists.
	ChannelServiceable ChannelConditionType = "Serviceable"

	// Routable means the virtual service forwarding traffic from the channel service to the
	// bus is created.
	ChannelRoutable ChannelConditionType = "Routeable"

	// Provisioned means the channel backing construct on the bus middleware has been set up.
	ChannelProvisioned ChannelConditionType = "Provisioned"
)

// ChannelCondition describes the state of a channel at a point in time.
type ChannelCondition struct {
	// Type of channel condition.
	Type ChannelConditionType `json:"type"`
	// Status of the condition, one of True, False, Unknown.
	Status v1.ConditionStatus `json:"status"`
	// The last time this condition was updated.
	LastUpdateTime meta_v1.Time `json:"lastUpdateTime,omitempty"`
	// Last time the condition transitioned from one status to another.
	LastTransitionTime meta_v1.Time `json:"lastTransitionTime,omitempty"`
	// The reason for the condition's last transition.
	Reason string `json:"reason,omitempty"`
	// A human readable message indicating details about the transition.
	Message string `json:"message,omitempty"`
}

// ChannelStatus (computed) for a channel
type ChannelStatus struct {
	// A reference to the k8s Service backing this channel, if successfully synced.
	Service *v1.LocalObjectReference `json:"service,omitempty"`

	// A reference to the istio VirtualService backing this channel, if successfully synced.
	VirtualService *v1.LocalObjectReference `json:"virtualService,omitempty"`

	// Represents the latest available observations of a channel's current state.
	// +patchMergeKey=type
	// +patchStrategy=merge
	Conditions []ChannelCondition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"`

	// DomainInternal holds the top-level domain that will distribute traffic
	// over the provided targets from inside the cluster. It generally has the
	// form {channel}.{namespace}.svc.cluster.local
	// +optional
	DomainInternal string `json:"domainInternal,omitempty"`
}

func (cs *ChannelStatus) GetCondition(t ChannelConditionType) *ChannelCondition {
	for _, cond := range cs.Conditions {
		if cond.Type == t {
			return &cond
		}
	}
	return nil
}

func (c *Channel) GetSpecJSON() ([]byte, error) {
	return json.Marshal(c.Spec)
}

// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object

// ChannelList returned in list operations
type ChannelList struct {
	meta_v1.TypeMeta `json:",inline"`
	meta_v1.ListMeta `json:"metadata"`
	Items            []Channel `json:"items"`
}
<|endoftext|>


<commit_before>package origin

import (
	"io/ioutil"
	"regexp"
	"time"

	"github.com/golang/glog"

	kapi "k8s.io/kubernetes/pkg/api"
	kapierror "k8s.io/kubernetes/pkg/api/errors"
	"k8s.io/kubernetes/pkg/controller/serviceaccount"
	"k8s.io/kubernetes/pkg/fields"
	"k8s.io/kubernetes/pkg/labels"
	"k8s.io/kubernetes/pkg/util"

	"github.com/openshift/origin/pkg/cmd/admin/policy"

	authorizationapi "github.com/openshift/origin/pkg/authorization/api"
	clusterpolicyregistry "github.com/openshift/origin/pkg/authorization/registry/clusterpolicy"
	clusterpolicystorage "github.com/openshift/origin/pkg/authorization/registry/clusterpolicy/etcd"
	"github.com/openshift/origin/pkg/cmd/server/admin"
	"github.com/openshift/origin/pkg/cmd/server/bootstrappolicy"
)

// ensureOpenShiftSharedResourcesNamespace is called as part of global policy initialization to ensure shared namespace exists
func (c *MasterConfig) ensureOpenShiftSharedResourcesNamespace() {
	if _, err := c.KubeClient().Namespaces().Get(c.Options.PolicyConfig.OpenShiftSharedResourcesNamespace); kapierror.IsNotFound(err) {
		namespace := &kapi.Namespace{
			ObjectMeta: kapi.ObjectMeta{Name: c.Options.PolicyConfig.OpenShiftSharedResourcesNamespace},
		}
		_, err = c.KubeClient().Namespaces().Create(namespace)
		if err != nil {
			glog.Errorf("Error creating namespace: %v due to %v\n", namespace, err)
			return
		}

		c.ensureNamespaceServiceAccountRoleBindings(namespace)
	}
}

// ensureOpenShiftInfraNamespace is called as part of global policy initialization to ensure infra namespace exists
func (c *MasterConfig) ensureOpenShiftInfraNamespace() {
	ns := c.Options.PolicyConfig.OpenShiftInfrastructureNamespace

	// Ensure namespace exists
	namespace, err := c.KubeClient().Namespaces().Create(&kapi.Namespace{ObjectMeta: kapi.ObjectMeta{Name: ns}})
	if err != nil && !kapierror.IsAlreadyExists(err) {
		glog.Errorf("Error creating namespace %s: %v", ns, err)
	}

	// Ensure service accounts exist
	serviceAccounts := []string{
		c.BuildControllerServiceAccount, c.DeploymentControllerServiceAccount, c.ReplicationControllerServiceAccount,
		c.JobControllerServiceAccount, c.HPAControllerServiceAccount,
	}
	for _, serviceAccountName := range serviceAccounts {
		_, err := c.KubeClient().ServiceAccounts(ns).Create(&kapi.ServiceAccount{ObjectMeta: kapi.ObjectMeta{Name: serviceAccountName}})
		if err != nil && !kapierror.IsAlreadyExists(err) {
			glog.Errorf("Error creating service account %s/%s: %v", ns, serviceAccountName, err)
		}
	}

	// Ensure service account cluster role bindings exist
	clusterRolesToSubjects := map[string][]kapi.ObjectReference{
		bootstrappolicy.BuildControllerRoleName:       {{Namespace: ns, Name: c.BuildControllerServiceAccount, Kind: "ServiceAccount"}},
		bootstrappolicy.DeploymentControllerRoleName:  {{Namespace: ns, Name: c.DeploymentControllerServiceAccount, Kind: "ServiceAccount"}},
		bootstrappolicy.ReplicationControllerRoleName: {{Namespace: ns, Name: c.ReplicationControllerServiceAccount, Kind: "ServiceAccount"}},
		bootstrappolicy.JobControllerRoleName:         {{Namespace: ns, Name: c.JobControllerServiceAccount, Kind: "ServiceAccount"}},
		bootstrappolicy.HPAControllerRoleName:         {{Namespace: ns, Name: c.HPAControllerServiceAccount, Kind: "ServiceAccount"}},
	}
	roleAccessor := policy.NewClusterRoleBindingAccessor(c.ServiceAccountRoleBindingClient())
	for clusterRole, subjects := range clusterRolesToSubjects {
		addRole := &policy.RoleModificationOptions{
			RoleName:            clusterRole,
			RoleBindingAccessor: roleAccessor,
			Subjects:            subjects,
		}
		if err := addRole.AddRole(); err != nil {
			glog.Errorf("Could not add %v subjects to the %v cluster role: %v\n", subjects, clusterRole, err)
		} else {
			glog.V(2).Infof("Added %v subjects to the %v cluster role: %v\n", subjects, clusterRole, err)
		}
	}

	c.ensureNamespaceServiceAccountRoleBindings(namespace)
}

// ensureDefaultNamespaceServiceAccountRoles initializes roles for service accounts in the default namespace
func (c *MasterConfig) ensureDefaultNamespaceServiceAccountRoles() {
	// Wait for the default namespace
	var namespace *kapi.Namespace
	for i := 0; i < 30; i++ {
		ns, err := c.KubeClient().Namespaces().Get(kapi.NamespaceDefault)
		if err == nil {
			namespace = ns
			break
		}
		if kapierror.IsNotFound(err) {
			time.Sleep(time.Second)
			continue
		}
		glog.Errorf("Error adding finding to %q namespace: %v", kapi.NamespaceDefault, err)
		return
	}
	if namespace == nil {
		glog.Errorf("Namespace %q not found, could not initialize the %q namespace", kapi.NamespaceDefault, kapi.NamespaceDefault)
		return
	}

	c.ensureNamespaceServiceAccountRoleBindings(namespace)
}

// ensureNamespaceServiceAccountRoleBindings initializes roles for service accounts in the namespace
func (c *MasterConfig) ensureNamespaceServiceAccountRoleBindings(namespace *kapi.Namespace) {
	const ServiceAccountRolesInitializedAnnotation = "openshift.io/sa.initialized-roles"

	// Short-circuit if we're already initialized
	if namespace.Annotations[ServiceAccountRolesInitializedAnnotation] == "true" {
		return
	}

	hasErrors := false
	for _, binding := range bootstrappolicy.GetBootstrapServiceAccountProjectRoleBindings(namespace.Name) {
		addRole := &policy.RoleModificationOptions{
			RoleName:            binding.RoleRef.Name,
			RoleNamespace:       binding.RoleRef.Namespace,
			RoleBindingAccessor: policy.NewLocalRoleBindingAccessor(namespace.Name, c.ServiceAccountRoleBindingClient()),
			Subjects:            binding.Subjects,
		}
		if err := addRole.AddRole(); err != nil {
			glog.Errorf("Could not add service accounts to the %v role in the %q namespace: %v\n", binding.RoleRef.Name, namespace.Name, err)
			hasErrors = true
		}
	}

	// If we had errors, don't register initialization so we can try again
	if hasErrors {
		return
	}

	if namespace.Annotations == nil {
		namespace.Annotations = map[string]string{}
	}
	namespace.Annotations[ServiceAccountRolesInitializedAnnotation] = "true"
	if _, err := c.KubeClient().Namespaces().Update(namespace); err != nil {
		glog.Errorf("Error recording adding service account roles to %q namespace: %v", namespace.Name, err)
	}
}

func (c *MasterConfig) ensureDefaultSecurityContextConstraints() {
	sccList, err := c.KubeClient().SecurityContextConstraints().List(labels.Everything(), fields.Everything())
	if err != nil {
		glog.Errorf("Unable to initialize security context constraints: %v.  This may prevent the creation of pods", err)
		return
	}
	if len(sccList.Items) > 0 {
		return
	}

	glog.Infof("No security context constraints detected, adding defaults")

	// add the build user to the privileged SCC access
	ns := c.Options.PolicyConfig.OpenShiftInfrastructureNamespace
	buildControllerUsername := serviceaccount.MakeUsername(ns, c.BuildControllerServiceAccount)
	bootstrapSCCGroups, bootstrapSCCUsers := bootstrappolicy.GetBoostrapSCCAccess()
	bootstrapSCCUsers[bootstrappolicy.SecurityContextConstraintPrivileged] = append(bootstrapSCCUsers[bootstrappolicy.SecurityContextConstraintPrivileged], buildControllerUsername)

	for _, scc := range bootstrappolicy.GetBootstrapSecurityContextConstraints(bootstrapSCCGroups, bootstrapSCCUsers) {
		_, err = c.KubeClient().SecurityContextConstraints().Create(&scc)
		if err != nil {
			glog.Errorf("Unable to create default security context constraint %s.  Got error: %v", scc.Name, err)
		}
	}
}

// ensureComponentAuthorizationRules initializes the cluster policies
func (c *MasterConfig) ensureComponentAuthorizationRules() {
	clusterPolicyRegistry := clusterpolicyregistry.NewRegistry(clusterpolicystorage.NewStorage(c.EtcdHelper))
	ctx := kapi.WithNamespace(kapi.NewContext(), "")

	if _, err := clusterPolicyRegistry.GetClusterPolicy(ctx, authorizationapi.PolicyName); kapierror.IsNotFound(err) {
		glog.Infof("No cluster policy found.  Creating bootstrap policy based on: %v", c.Options.PolicyConfig.BootstrapPolicyFile)

		if err := admin.OverwriteBootstrapPolicy(c.EtcdHelper, c.Options.PolicyConfig.BootstrapPolicyFile, admin.CreateBootstrapPolicyFileFullCommand, true, ioutil.Discard); err != nil {
			glog.Errorf("Error creating bootstrap policy: %v", err)
		}

	} else {
		glog.V(2).Infof("Ignoring bootstrap policy file because cluster policy found")
	}
}

// ensureCORSAllowedOrigins takes a string list of origins and attempts to covert them to CORS origin
// regexes, or exits if it cannot.
func (c *MasterConfig) ensureCORSAllowedOrigins() []*regexp.Regexp {
	if len(c.Options.CORSAllowedOrigins) == 0 {
		return []*regexp.Regexp{}
	}
	allowedOriginRegexps, err := util.CompileRegexps(util.StringList(c.Options.CORSAllowedOrigins))
	if err != nil {
		glog.Fatalf("Invalid --cors-allowed-origins: %v", err)
	}
	return allowedOriginRegexps
}
<commit_msg>Fix namespace initialization<commit_after>package origin

import (
	"io/ioutil"
	"regexp"
	"time"

	"github.com/golang/glog"

	kapi "k8s.io/kubernetes/pkg/api"
	kapierror "k8s.io/kubernetes/pkg/api/errors"
	"k8s.io/kubernetes/pkg/controller/serviceaccount"
	"k8s.io/kubernetes/pkg/fields"
	"k8s.io/kubernetes/pkg/labels"
	"k8s.io/kubernetes/pkg/util"

	"github.com/openshift/origin/pkg/cmd/admin/policy"

	authorizationapi "github.com/openshift/origin/pkg/authorization/api"
	clusterpolicyregistry "github.com/openshift/origin/pkg/authorization/registry/clusterpolicy"
	clusterpolicystorage "github.com/openshift/origin/pkg/authorization/registry/clusterpolicy/etcd"
	"github.com/openshift/origin/pkg/cmd/server/admin"
	"github.com/openshift/origin/pkg/cmd/server/bootstrappolicy"
)

// ensureOpenShiftSharedResourcesNamespace is called as part of global policy initialization to ensure shared namespace exists
func (c *MasterConfig) ensureOpenShiftSharedResourcesNamespace() {
	if _, err := c.KubeClient().Namespaces().Get(c.Options.PolicyConfig.OpenShiftSharedResourcesNamespace); kapierror.IsNotFound(err) {
		namespace, createErr := c.KubeClient().Namespaces().Create(&kapi.Namespace{ObjectMeta: kapi.ObjectMeta{Name: c.Options.PolicyConfig.OpenShiftSharedResourcesNamespace}})
		if createErr != nil {
			glog.Errorf("Error creating namespace: %v due to %v\n", c.Options.PolicyConfig.OpenShiftSharedResourcesNamespace, createErr)
			return
		}

		c.ensureNamespaceServiceAccountRoleBindings(namespace)
	}
}

// ensureOpenShiftInfraNamespace is called as part of global policy initialization to ensure infra namespace exists
func (c *MasterConfig) ensureOpenShiftInfraNamespace() {
	ns := c.Options.PolicyConfig.OpenShiftInfrastructureNamespace

	// Ensure namespace exists
	namespace, err := c.KubeClient().Namespaces().Create(&kapi.Namespace{ObjectMeta: kapi.ObjectMeta{Name: ns}})
	if kapierror.IsAlreadyExists(err) {
		// Get the persisted namespace
		namespace, err = c.KubeClient().Namespaces().Get(ns)
		if err != nil {
			glog.Errorf("Error getting namespace %s: %v", ns, err)
			return
		}
	} else if err != nil {
		glog.Errorf("Error creating namespace %s: %v", ns, err)
		return
	}

	// Ensure service accounts exist
	serviceAccounts := []string{
		c.BuildControllerServiceAccount, c.DeploymentControllerServiceAccount, c.ReplicationControllerServiceAccount,
		c.JobControllerServiceAccount, c.HPAControllerServiceAccount,
	}
	for _, serviceAccountName := range serviceAccounts {
		_, err := c.KubeClient().ServiceAccounts(ns).Create(&kapi.ServiceAccount{ObjectMeta: kapi.ObjectMeta{Name: serviceAccountName}})
		if err != nil && !kapierror.IsAlreadyExists(err) {
			glog.Errorf("Error creating service account %s/%s: %v", ns, serviceAccountName, err)
		}
	}

	// Ensure service account cluster role bindings exist
	clusterRolesToSubjects := map[string][]kapi.ObjectReference{
		bootstrappolicy.BuildControllerRoleName:       {{Namespace: ns, Name: c.BuildControllerServiceAccount, Kind: "ServiceAccount"}},
		bootstrappolicy.DeploymentControllerRoleName:  {{Namespace: ns, Name: c.DeploymentControllerServiceAccount, Kind: "ServiceAccount"}},
		bootstrappolicy.ReplicationControllerRoleName: {{Namespace: ns, Name: c.ReplicationControllerServiceAccount, Kind: "ServiceAccount"}},
		bootstrappolicy.JobControllerRoleName:         {{Namespace: ns, Name: c.JobControllerServiceAccount, Kind: "ServiceAccount"}},
		bootstrappolicy.HPAControllerRoleName:         {{Namespace: ns, Name: c.HPAControllerServiceAccount, Kind: "ServiceAccount"}},
	}
	roleAccessor := policy.NewClusterRoleBindingAccessor(c.ServiceAccountRoleBindingClient())
	for clusterRole, subjects := range clusterRolesToSubjects {
		addRole := &policy.RoleModificationOptions{
			RoleName:            clusterRole,
			RoleBindingAccessor: roleAccessor,
			Subjects:            subjects,
		}
		if err := addRole.AddRole(); err != nil {
			glog.Errorf("Could not add %v subjects to the %v cluster role: %v\n", subjects, clusterRole, err)
		} else {
			glog.V(2).Infof("Added %v subjects to the %v cluster role: %v\n", subjects, clusterRole, err)
		}
	}

	c.ensureNamespaceServiceAccountRoleBindings(namespace)
}

// ensureDefaultNamespaceServiceAccountRoles initializes roles for service accounts in the default namespace
func (c *MasterConfig) ensureDefaultNamespaceServiceAccountRoles() {
	// Wait for the default namespace
	var namespace *kapi.Namespace
	for i := 0; i < 30; i++ {
		ns, err := c.KubeClient().Namespaces().Get(kapi.NamespaceDefault)
		if err == nil {
			namespace = ns
			break
		}
		if kapierror.IsNotFound(err) {
			time.Sleep(time.Second)
			continue
		}
		glog.Errorf("Error adding service account roles to %q namespace: %v", kapi.NamespaceDefault, err)
		return
	}
	if namespace == nil {
		glog.Errorf("Namespace %q not found, could not initialize the %q namespace", kapi.NamespaceDefault, kapi.NamespaceDefault)
		return
	}

	c.ensureNamespaceServiceAccountRoleBindings(namespace)
}

// ensureNamespaceServiceAccountRoleBindings initializes roles for service accounts in the namespace
func (c *MasterConfig) ensureNamespaceServiceAccountRoleBindings(namespace *kapi.Namespace) {
	const ServiceAccountRolesInitializedAnnotation = "openshift.io/sa.initialized-roles"

	// Short-circuit if we're already initialized
	if namespace.Annotations[ServiceAccountRolesInitializedAnnotation] == "true" {
		return
	}

	hasErrors := false
	for _, binding := range bootstrappolicy.GetBootstrapServiceAccountProjectRoleBindings(namespace.Name) {
		addRole := &policy.RoleModificationOptions{
			RoleName:            binding.RoleRef.Name,
			RoleNamespace:       binding.RoleRef.Namespace,
			RoleBindingAccessor: policy.NewLocalRoleBindingAccessor(namespace.Name, c.ServiceAccountRoleBindingClient()),
			Subjects:            binding.Subjects,
		}
		if err := addRole.AddRole(); err != nil {
			glog.Errorf("Could not add service accounts to the %v role in the %q namespace: %v\n", binding.RoleRef.Name, namespace.Name, err)
			hasErrors = true
		}
	}

	// If we had errors, don't register initialization so we can try again
	if hasErrors {
		return
	}

	if namespace.Annotations == nil {
		namespace.Annotations = map[string]string{}
	}
	namespace.Annotations[ServiceAccountRolesInitializedAnnotation] = "true"
	if _, err := c.KubeClient().Namespaces().Update(namespace); err != nil {
		glog.Errorf("Error recording adding service account roles to %q namespace: %v", namespace.Name, err)
	}
}

func (c *MasterConfig) ensureDefaultSecurityContextConstraints() {
	sccList, err := c.KubeClient().SecurityContextConstraints().List(labels.Everything(), fields.Everything())
	if err != nil {
		glog.Errorf("Unable to initialize security context constraints: %v.  This may prevent the creation of pods", err)
		return
	}
	if len(sccList.Items) > 0 {
		return
	}

	glog.Infof("No security context constraints detected, adding defaults")

	// add the build user to the privileged SCC access
	ns := c.Options.PolicyConfig.OpenShiftInfrastructureNamespace
	buildControllerUsername := serviceaccount.MakeUsername(ns, c.BuildControllerServiceAccount)
	bootstrapSCCGroups, bootstrapSCCUsers := bootstrappolicy.GetBoostrapSCCAccess()
	bootstrapSCCUsers[bootstrappolicy.SecurityContextConstraintPrivileged] = append(bootstrapSCCUsers[bootstrappolicy.SecurityContextConstraintPrivileged], buildControllerUsername)

	for _, scc := range bootstrappolicy.GetBootstrapSecurityContextConstraints(bootstrapSCCGroups, bootstrapSCCUsers) {
		_, err = c.KubeClient().SecurityContextConstraints().Create(&scc)
		if err != nil {
			glog.Errorf("Unable to create default security context constraint %s.  Got error: %v", scc.Name, err)
		}
	}
}

// ensureComponentAuthorizationRules initializes the cluster policies
func (c *MasterConfig) ensureComponentAuthorizationRules() {
	clusterPolicyRegistry := clusterpolicyregistry.NewRegistry(clusterpolicystorage.NewStorage(c.EtcdHelper))
	ctx := kapi.WithNamespace(kapi.NewContext(), "")

	if _, err := clusterPolicyRegistry.GetClusterPolicy(ctx, authorizationapi.PolicyName); kapierror.IsNotFound(err) {
		glog.Infof("No cluster policy found.  Creating bootstrap policy based on: %v", c.Options.PolicyConfig.BootstrapPolicyFile)

		if err := admin.OverwriteBootstrapPolicy(c.EtcdHelper, c.Options.PolicyConfig.BootstrapPolicyFile, admin.CreateBootstrapPolicyFileFullCommand, true, ioutil.Discard); err != nil {
			glog.Errorf("Error creating bootstrap policy: %v", err)
		}

	} else {
		glog.V(2).Infof("Ignoring bootstrap policy file because cluster policy found")
	}
}

// ensureCORSAllowedOrigins takes a string list of origins and attempts to covert them to CORS origin
// regexes, or exits if it cannot.
func (c *MasterConfig) ensureCORSAllowedOrigins() []*regexp.Regexp {
	if len(c.Options.CORSAllowedOrigins) == 0 {
		return []*regexp.Regexp{}
	}
	allowedOriginRegexps, err := util.CompileRegexps(util.StringList(c.Options.CORSAllowedOrigins))
	if err != nil {
		glog.Fatalf("Invalid --cors-allowed-origins: %v", err)
	}
	return allowedOriginRegexps
}
<|endoftext|>


<commit_before>// +skip_license_check

/*
This file contains portions of code directly taken from the 'xenolf/lego' project.
A copy of the license for this code can be found in the file named LICENSE in
this directory.
*/

package util

import (
	"fmt"

	"github.com/miekg/dns"
)

// DNS01LookupFQDN returns a DNS name which will be updated to solve the dns-01
// challenge
// TODO: move this into the pkg/acme package
func DNS01LookupFQDN(domain string, followCNAME bool, nameservers ...string) (string, error) {
	fqdn := fmt.Sprintf("_acme-challenge.%s.", domain)

	// Check if the domain has CNAME then return that
	if followCNAME {
		var err error
		fqdn, err = updateDomainWithCName(fqdn, nameservers)
		if err != nil {
			return "", err
		}
	}

	return fqdn, nil
}

// FindBestMatch returns the longest match for a given domain within a list of domains
func FindBestMatch(query string, domains ...string) (string, error) {
	var maxSoFar int
	var longest string

	for _, domain := range domains {
		if query == domain {
			// Found exact match
			return domain, nil
		}

		maxHere := dns.CompareDomainName(query, domain)
		if maxHere > maxSoFar && dns.IsSubDomain(domain, query) {
			maxSoFar = maxHere
			longest = domain
		}
	}

	if len(longest) == 0 {
		return "", fmt.Errorf("query: %v has no matches", query)
	}
	return longest, nil
}
<commit_msg>rename a function call<commit_after>// +skip_license_check

/*
This file contains portions of code directly taken from the 'xenolf/lego' project.
A copy of the license for this code can be found in the file named LICENSE in
this directory.
*/

package util

import (
	"fmt"

	"github.com/miekg/dns"
)

// DNS01LookupFQDN returns a DNS name which will be updated to solve the dns-01
// challenge
// TODO: move this into the pkg/acme package
func DNS01LookupFQDN(domain string, followCNAME bool, nameservers ...string) (string, error) {
	fqdn := fmt.Sprintf("_acme-challenge.%s.", domain)

	// Check if the domain has CNAME then return that
	if followCNAME {
		var err error
		fqdn, err = followCNAMEs(fqdn, nameservers)
		if err != nil {
			return "", err
		}
	}

	return fqdn, nil
}

// FindBestMatch returns the longest match for a given domain within a list of domains
func FindBestMatch(query string, domains ...string) (string, error) {
	var maxSoFar int
	var longest string

	for _, domain := range domains {
		if query == domain {
			// Found exact match
			return domain, nil
		}

		maxHere := dns.CompareDomainName(query, domain)
		if maxHere > maxSoFar && dns.IsSubDomain(domain, query) {
			maxSoFar = maxHere
			longest = domain
		}
	}

	if len(longest) == 0 {
		return "", fmt.Errorf("query: %v has no matches", query)
	}
	return longest, nil
}
<|endoftext|>


<commit_before>/*
Copyright 2020 The Kubernetes Authors.

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

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

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

package release

import (
	"strings"

	"github.com/blang/semver"
	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"
	"k8s.io/release/pkg/git"
	"sigs.k8s.io/release-utils/util"
)

// GitObjectPusher is an object that pushes things to a gitrepo
type GitObjectPusher struct {
	repo git.Repo
	opts *GitObjectPusherOptions
}

var dryRunLabel = map[bool]string{true: " --dry-run", false: ""}

// GitObjectPusherOptions struct to hold the pusher options
type GitObjectPusherOptions struct {
	// Flago simulate pushes, passes --dry-run to git
	DryRun bool

	// Number of times to retry pushes
	MaxRetries int

	// Path to the repository
	RepoPath string
}

// NewGitPusher returns a new git object pusher
func NewGitPusher(opts *GitObjectPusherOptions) (*GitObjectPusher, error) {
	repo, err := git.OpenRepo(opts.RepoPath)
	if err != nil {
		return nil, errors.Wrap(err, "while opening repository")
	}

	logrus.Infof("Checkout %s branch to push objects", git.DefaultBranch)
	if err := repo.Checkout(git.DefaultBranch); err != nil {
		return nil, errors.Wrapf(err, "checking out %s branch", git.DefaultBranch)
	}

	// Pass the dry-run flag to the repo
	if opts.DryRun {
		logrus.Debug("Setting dry run flag to repository, pushing will be simuluated")
		repo.SetDry()
	}

	// Set the number of retries for the git operations:
	repo.SetMaxRetries(opts.MaxRetries)

	return &GitObjectPusher{
		repo: *repo,
		opts: opts,
	}, nil
}

// PushBranches Convenience method to push a list of branches
func (gp *GitObjectPusher) PushBranches(branchList []string) error {
	for _, branchName := range branchList {
		if err := gp.PushBranch(branchName); err != nil {
			return errors.Wrapf(err, "pushing %s branch", branchName)
		}
	}
	logrus.Infof("Successfully pushed %d branches", len(branchList))
	return nil
}

// PushBranch pushes a branch to the repository
//  this function is idempotent.
func (gp *GitObjectPusher) PushBranch(branchName string) error {
	// Check if the branch name is correct
	if err := gp.checkBranchName(branchName); err != nil {
		return errors.Wrap(err, "checking branch name")
	}

	// To be able to push a branch the ref has to exist in the local repo:
	branchExists, err := gp.repo.HasBranch(branchName)
	if err != nil {
		return errors.Wrap(err, "checking if branch already exists locally")
	}
	if !branchExists {
		return errors.Errorf("unable to push branch %s, it does not exist in the local repo", branchName)
	}

	// Checkout the branch before merging
	logrus.Infof("Checking out branch %s to merge upstream changes", branchName)
	if err := gp.repo.Checkout(branchName); err != nil {
		return errors.Wrapf(err, "checking out branch %s", git.Remotify(branchName))
	}

	if err := gp.mergeRemoteIfRequired(branchName); err != nil {
		return errors.Wrap(err, "merge remote if required")
	}

	logrus.Infof("Pushing%s %s branch:", dryRunLabel[gp.opts.DryRun], branchName)
	if err := gp.repo.Push(branchName); err != nil {
		return errors.Wrapf(err, "pushing branch %s", branchName)
	}
	logrus.Infof("Branch %s pushed successfully", branchName)
	return nil
}

// PushTags convenience method to push a list of tags to the remote repo
func (gp *GitObjectPusher) PushTags(tagList []string) (err error) {
	for _, tag := range tagList {
		if err := gp.PushTag(tag); err != nil {
			return errors.Wrapf(err, "while pushing %s tag", tag)
		}
	}
	logrus.Infof("Pushed %d tags to the remote repo", len(tagList))
	return nil
}

// PushTag pushes a tag to the master repo
func (gp *GitObjectPusher) PushTag(newTag string) (err error) {
	// Verify that the tag is a valid tag
	if err := gp.checkTagName(newTag); err != nil {
		return errors.Wrap(err, "parsing version tag")
	}

	// Check if tag already exists
	currentTags, err := gp.repo.Tags()
	if err != nil {
		return errors.Wrap(err, "checking if tag exists")
	}

	// verify that the tag exists locally before trying to push
	tagExists := false
	for _, tag := range currentTags {
		if tag == newTag {
			tagExists = true
			break
		}
	}
	if !tagExists {
		return errors.Errorf("unable to push tag %s, it does not exist in the repo yet", newTag)
	}

	// CHeck if tag already exists in the remote repo
	tagExists, err = gp.repo.HasRemoteTag(newTag)
	if err != nil {
		return errors.Wrapf(err, "checking of tag %s exists", newTag)
	}

	// If the tag already exists in the remote, we return success
	if tagExists {
		logrus.Infof("Tag %s already exists in remote. Noop.", newTag)
		return nil
	}

	logrus.Infof("Pushing%s tag for version %s", dryRunLabel[gp.opts.DryRun], newTag)

	// Push the new tag, retrying up to opts.MaxRetries times
	if err := gp.repo.Push(newTag); err != nil {
		return errors.Wrapf(err, "pushing tag %s", newTag)
	}

	logrus.Infof("Successfully pushed tag %s", newTag)
	return nil
}

// checkTagName verifies that the specified tag name is valid
func (gp *GitObjectPusher) checkTagName(tagName string) error {
	_, err := util.TagStringToSemver(tagName)
	if err != nil {
		return errors.Wrap(err, "tranforming tag into semver")
	}
	return nil
}

// checkBranchName verifies that the branch name is valid
func (gp *GitObjectPusher) checkBranchName(branchName string) error {
	if !strings.HasPrefix(branchName, "release-") {
		return errors.New("Branch name has to start with release-")
	}
	versionTag := strings.TrimPrefix(branchName, "release-")
	// Add .0 and check is we get a valid semver
	_, err := semver.Parse(versionTag + ".0")
	if err != nil {
		return errors.Wrap(err, "parsing semantic version in branchname")
	}
	return nil
}

// PushMain pushes the main branch to the origin
func (gp *GitObjectPusher) PushMain() error {
	logrus.Infof("Checkout %s branch to push objects", git.DefaultBranch)
	if err := gp.repo.Checkout(git.DefaultBranch); err != nil {
		return errors.Wrapf(err, "checking out %s branch", git.DefaultBranch)
	}

	// logrun -v git status -s || return 1
	status, err := gp.repo.Status()
	if err != nil {
		return errors.Wrap(err, "while reading the repository status")
	}
	if status.String() == "" {
		logrus.Info("Repository status: no modified paths")
	} else {
		logrus.Info(status.String())
	}

	// logrun -v git show || return 1
	lastLog, err := gp.repo.ShowLastCommit()
	if err != nil {
		return errors.Wrap(err, "getting last commit data from log")
	}
	logrus.Info(lastLog)

	if err := gp.mergeRemoteIfRequired(git.DefaultBranch); err != nil {
		return errors.Wrap(err, "merge remote if required")
	}

	logrus.Infof("Pushing%s %s branch", dryRunLabel[gp.opts.DryRun], git.DefaultBranch)

	// logrun -s git push$dryrun_flag origin master || return 1
	if err := gp.repo.Push(git.DefaultBranch); err != nil {
		return errors.Wrapf(err, "pushing %s branch", git.DefaultBranch)
	}
	return nil
}

func (gp *GitObjectPusher) mergeRemoteIfRequired(branch string) error {
	branch = git.Remotify(branch)
	logrus.Infof("Merging %s branch if required", branch)

	logrus.Infof("Fetching from %s", git.DefaultRemote)
	if _, err := gp.repo.FetchRemote(git.DefaultRemote); err != nil {
		return errors.Wrap(err, "fetch remote")
	}

	branchExists, err := gp.repo.HasRemoteBranch(branch)
	if err != nil {
		return errors.Wrapf(
			err, "checking if branch %s exists in repo remote", branch,
		)
	}
	if !branchExists {
		logrus.Infof(
			"Git repository does not have remote branch %s, not attempting merge", branch,
		)
		return nil
	}

	logrus.Infof("Merging %s branch", branch)
	if err := gp.repo.Merge(branch); err != nil {
		return errors.Wrapf(
			err, "merging remote branch %s to local repo", branch,
		)
	}

	logrus.Info("Local branch is now up to date")
	return nil
}
<commit_msg>Pass branch without remote to HasRemoteBranch()<commit_after>/*
Copyright 2020 The Kubernetes Authors.

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

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

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

package release

import (
	"strings"

	"github.com/blang/semver"
	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"
	"k8s.io/release/pkg/git"
	"sigs.k8s.io/release-utils/util"
)

// GitObjectPusher is an object that pushes things to a gitrepo
type GitObjectPusher struct {
	repo git.Repo
	opts *GitObjectPusherOptions
}

var dryRunLabel = map[bool]string{true: " --dry-run", false: ""}

// GitObjectPusherOptions struct to hold the pusher options
type GitObjectPusherOptions struct {
	// Flago simulate pushes, passes --dry-run to git
	DryRun bool

	// Number of times to retry pushes
	MaxRetries int

	// Path to the repository
	RepoPath string
}

// NewGitPusher returns a new git object pusher
func NewGitPusher(opts *GitObjectPusherOptions) (*GitObjectPusher, error) {
	repo, err := git.OpenRepo(opts.RepoPath)
	if err != nil {
		return nil, errors.Wrap(err, "while opening repository")
	}

	logrus.Infof("Checkout %s branch to push objects", git.DefaultBranch)
	if err := repo.Checkout(git.DefaultBranch); err != nil {
		return nil, errors.Wrapf(err, "checking out %s branch", git.DefaultBranch)
	}

	// Pass the dry-run flag to the repo
	if opts.DryRun {
		logrus.Debug("Setting dry run flag to repository, pushing will be simuluated")
		repo.SetDry()
	}

	// Set the number of retries for the git operations:
	repo.SetMaxRetries(opts.MaxRetries)

	return &GitObjectPusher{
		repo: *repo,
		opts: opts,
	}, nil
}

// PushBranches Convenience method to push a list of branches
func (gp *GitObjectPusher) PushBranches(branchList []string) error {
	for _, branchName := range branchList {
		if err := gp.PushBranch(branchName); err != nil {
			return errors.Wrapf(err, "pushing %s branch", branchName)
		}
	}
	logrus.Infof("Successfully pushed %d branches", len(branchList))
	return nil
}

// PushBranch pushes a branch to the repository
//  this function is idempotent.
func (gp *GitObjectPusher) PushBranch(branchName string) error {
	// Check if the branch name is correct
	if err := gp.checkBranchName(branchName); err != nil {
		return errors.Wrap(err, "checking branch name")
	}

	// To be able to push a branch the ref has to exist in the local repo:
	branchExists, err := gp.repo.HasBranch(branchName)
	if err != nil {
		return errors.Wrap(err, "checking if branch already exists locally")
	}
	if !branchExists {
		return errors.Errorf("unable to push branch %s, it does not exist in the local repo", branchName)
	}

	// Checkout the branch before merging
	logrus.Infof("Checking out branch %s to merge upstream changes", branchName)
	if err := gp.repo.Checkout(branchName); err != nil {
		return errors.Wrapf(err, "checking out branch %s", git.Remotify(branchName))
	}

	if err := gp.mergeRemoteIfRequired(branchName); err != nil {
		return errors.Wrap(err, "merge remote if required")
	}

	logrus.Infof("Pushing%s %s branch:", dryRunLabel[gp.opts.DryRun], branchName)
	if err := gp.repo.Push(branchName); err != nil {
		return errors.Wrapf(err, "pushing branch %s", branchName)
	}
	logrus.Infof("Branch %s pushed successfully", branchName)
	return nil
}

// PushTags convenience method to push a list of tags to the remote repo
func (gp *GitObjectPusher) PushTags(tagList []string) (err error) {
	for _, tag := range tagList {
		if err := gp.PushTag(tag); err != nil {
			return errors.Wrapf(err, "while pushing %s tag", tag)
		}
	}
	logrus.Infof("Pushed %d tags to the remote repo", len(tagList))
	return nil
}

// PushTag pushes a tag to the master repo
func (gp *GitObjectPusher) PushTag(newTag string) (err error) {
	// Verify that the tag is a valid tag
	if err := gp.checkTagName(newTag); err != nil {
		return errors.Wrap(err, "parsing version tag")
	}

	// Check if tag already exists
	currentTags, err := gp.repo.Tags()
	if err != nil {
		return errors.Wrap(err, "checking if tag exists")
	}

	// verify that the tag exists locally before trying to push
	tagExists := false
	for _, tag := range currentTags {
		if tag == newTag {
			tagExists = true
			break
		}
	}
	if !tagExists {
		return errors.Errorf("unable to push tag %s, it does not exist in the repo yet", newTag)
	}

	// CHeck if tag already exists in the remote repo
	tagExists, err = gp.repo.HasRemoteTag(newTag)
	if err != nil {
		return errors.Wrapf(err, "checking of tag %s exists", newTag)
	}

	// If the tag already exists in the remote, we return success
	if tagExists {
		logrus.Infof("Tag %s already exists in remote. Noop.", newTag)
		return nil
	}

	logrus.Infof("Pushing%s tag for version %s", dryRunLabel[gp.opts.DryRun], newTag)

	// Push the new tag, retrying up to opts.MaxRetries times
	if err := gp.repo.Push(newTag); err != nil {
		return errors.Wrapf(err, "pushing tag %s", newTag)
	}

	logrus.Infof("Successfully pushed tag %s", newTag)
	return nil
}

// checkTagName verifies that the specified tag name is valid
func (gp *GitObjectPusher) checkTagName(tagName string) error {
	_, err := util.TagStringToSemver(tagName)
	if err != nil {
		return errors.Wrap(err, "tranforming tag into semver")
	}
	return nil
}

// checkBranchName verifies that the branch name is valid
func (gp *GitObjectPusher) checkBranchName(branchName string) error {
	if !strings.HasPrefix(branchName, "release-") {
		return errors.New("Branch name has to start with release-")
	}
	versionTag := strings.TrimPrefix(branchName, "release-")
	// Add .0 and check is we get a valid semver
	_, err := semver.Parse(versionTag + ".0")
	if err != nil {
		return errors.Wrap(err, "parsing semantic version in branchname")
	}
	return nil
}

// PushMain pushes the main branch to the origin
func (gp *GitObjectPusher) PushMain() error {
	logrus.Infof("Checkout %s branch to push objects", git.DefaultBranch)
	if err := gp.repo.Checkout(git.DefaultBranch); err != nil {
		return errors.Wrapf(err, "checking out %s branch", git.DefaultBranch)
	}

	// logrun -v git status -s || return 1
	status, err := gp.repo.Status()
	if err != nil {
		return errors.Wrap(err, "while reading the repository status")
	}
	if status.String() == "" {
		logrus.Info("Repository status: no modified paths")
	} else {
		logrus.Info(status.String())
	}

	// logrun -v git show || return 1
	lastLog, err := gp.repo.ShowLastCommit()
	if err != nil {
		return errors.Wrap(err, "getting last commit data from log")
	}
	logrus.Info(lastLog)

	if err := gp.mergeRemoteIfRequired(git.DefaultBranch); err != nil {
		return errors.Wrap(err, "merge remote if required")
	}

	logrus.Infof("Pushing%s %s branch", dryRunLabel[gp.opts.DryRun], git.DefaultBranch)

	// logrun -s git push$dryrun_flag origin master || return 1
	if err := gp.repo.Push(git.DefaultBranch); err != nil {
		return errors.Wrapf(err, "pushing %s branch", git.DefaultBranch)
	}
	return nil
}

func (gp *GitObjectPusher) mergeRemoteIfRequired(branch string) error {
	branch = git.Remotify(branch)
	branchParts := strings.Split(branch, "/")
	logrus.Infof("Merging %s branch if required", branch)

	logrus.Infof("Fetching from %s", git.DefaultRemote)
	if _, err := gp.repo.FetchRemote(git.DefaultRemote); err != nil {
		return errors.Wrap(err, "fetch remote")
	}

	branchExists, err := gp.repo.HasRemoteBranch(branchParts[1])
	if err != nil {
		return errors.Wrapf(
			err, "checking if branch %s exists in repo remote", branch,
		)
	}
	if !branchExists {
		logrus.Infof(
			"Git repository does not have remote branch %s, not attempting merge", branch,
		)
		return nil
	}

	logrus.Infof("Merging %s branch", branch)
	if err := gp.repo.Merge(branch); err != nil {
		return errors.Wrapf(
			err, "merging remote branch %s to local repo", branch,
		)
	}

	logrus.Info("Local branch is now up to date")
	return nil
}
<|endoftext|>


<commit_before>// Copyright 2015-present Oursky Ltd.
//
// 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.

// +build zmq

package zmq

import (
	"errors"
	"fmt"
	"sync"
	"time"

	"github.com/Sirupsen/logrus"
	"github.com/zeromq/goczmq"
)

const (
	// HeartbeatInterval is the interval that broker and worker send
	// heartbeats to each other.
	HeartbeatInterval = time.Second
	// HeartbeatLiveness defines the liveness of each heartbeat. Generally
	// it should be >= 3, otherwise workers will keep being discarded and
	// reconnecting.
	HeartbeatLiveness = 3
)

var heartbeatIntervalMS = int(HeartbeatInterval.Seconds() * 1000)

const (
	// Ready is sent by worker to signal broker that it is ready to receive
	// jobs.
	Ready = "\001"
	// Heartbeat is sent by both broker and worker to signal a heartbeat.
	Heartbeat = "\002"
	// Shutdown is sent by worker while being killed (probably by CTRL C).
	// It is an addition to original PPP to shorten the time needed for
	// broker to detect a normal shutdown of worker.
	Shutdown = "\003"
)

// parcel is used to multiplex the chan with zmq worker
type parcel struct {
	respChan chan []byte
	frame    []byte
	retry    int
}

func newParcel(frame []byte) *parcel {
	return &parcel{
		respChan: make(chan []byte),
		frame:    frame,
		retry:    0,
	}
}

// Broker implements the Paranoid Pirate queue described as follows:
// Related RFC: https://rfc.zeromq.org/spec:6/PPP
// refs: http://zguide.zeromq.org/py:all#Robust-Reliable-Queuing-Paranoid-Pirate-Pattern
// with the addition of:
//
// 1. Shutdown signal, which signifies a normal termination of worker to provide
//    a fast path of worker removal
// TODO: channeler can be separated into a separate struct, communiate with
// broker using frontend chan, workers and pull/push sock address.
type Broker struct {
	// name is assume to be unique and used to construct the zmq address
	name string
	// backendAddr is the address to communicate with plugin
	backendAddr string
	// frontend chan is receive zmq messgae and handle at Channeler
	frontend chan [][]byte
	// recvChan receive RPC request and dispatch to zmq Run Loop using
	// push/pull zsock
	recvChan chan *parcel
	// addressChan is use zmq worker addess as key to route the message to
	// correct go chan
	addressChan map[string]chan []byte
	// for RPC timeout, used by Channeler
	timeout chan string
	// determine how long will timeout happen, relative to the
	// HeartBeatInterval
	timeoutInterval time.Duration
	workers         workerQueue
	logger          *logrus.Entry
	// for stopping the channeler
	stop chan int
	// internal state for stopping the zmq Run when true.
	// Should use the stop chan to stop. The stop chan will set this variable.
	stopping bool
}

// NewBroker returns a new *Broker.
func NewBroker(name, backendAddr string, timeoutInterval int) (*Broker, error) {
	namedLogger := log.WithFields(logrus.Fields{
		"plugin": name,
		"eaddr":  backendAddr,
	})

	broker := &Broker{
		name:            name,
		backendAddr:     backendAddr,
		frontend:        make(chan [][]byte, 10),
		recvChan:        make(chan *parcel, 10),
		addressChan:     map[string]chan []byte{},
		timeout:         make(chan string),
		timeoutInterval: time.Duration(timeoutInterval),
		workers:         newWorkerQueue(),
		logger:          namedLogger,
		stop:            make(chan int),
		stopping:        false,
	}

	go broker.Run()
	go broker.Channeler()
	return broker, nil
}

// Run the Broker and listens for zmq requests.
func (lb *Broker) Run() {
	backend, err := goczmq.NewRouter(lb.backendAddr)
	if err != nil {
		panic(err)
	}
	defer backend.Destroy()

	pull, err := goczmq.NewPull(fmt.Sprintf("inproc://chanpipeline%d", lb.name))
	if err != nil {
		panic(err)
	}
	defer pull.Destroy()

	backendPoller, err := goczmq.NewPoller(backend, pull)
	if err != nil {
		panic(err)
	}

	heartbeatAt := time.Now().Add(HeartbeatInterval)
	for {
		sock := backendPoller.Wait(heartbeatIntervalMS)
		lb.workers.Lock()

		switch sock {
		case backend:
			frames, err := backend.RecvMessage()
			if err != nil {
				panic(err)
			}

			address := string(frames[0])
			msg := frames[1:]
			tErr := lb.workers.Tick(newWorker(address))
			if tErr != nil {
				status := string(msg[0])
				if status != Ready {
					lb.logger.Warnln(tErr)
				}
			}
			if len(msg) == 1 {
				status := string(msg[0])
				lb.handleWorkerStatus(address, status)
			} else {
				lb.frontend <- msg
				lb.logger.Debugf("zmq/broker: plugin => server: %#x, %s\n", msg[0], msg)
			}
		case pull:
			frames, err := pull.RecvMessage()
			if err != nil {
				panic(err)
			}
			backend.SendMessage(frames)
		case nil:
			// do nothing
		default:
			panic("zmq/broker: received unknown socket")
		}

		if heartbeatAt.Before(time.Now()) {
			for _, worker := range lb.workers.pworkers {
				msg := [][]byte{
					[]byte(worker.address),
					[]byte(Heartbeat),
				}
				backend.SendMessage(msg)
			}
			heartbeatAt = time.Now().Add(HeartbeatInterval)
		}

		lb.workers.Purge()
		lb.workers.Unlock()
		if lb.stopping {
			return
		}
	}
}

// Channeler accept message from RPC and dispatch to zmq if available.
// It retry and timeout the request if the zmq worker is not yet available.
func (lb *Broker) Channeler() {
	lb.logger.Infof("zmq channeler running %p\n", lb)
	defer lb.logger.Infof("zmq channeler stopped %p!\n", lb)
	push, err := goczmq.NewPush(fmt.Sprintf("inproc://chanpipeline%d", lb.name))
	if err != nil {
		panic(err)
	}
	defer push.Destroy()
	for {
		select {
		case frames := <-lb.frontend:
			lb.logger.Debugf("zmq/broker: zmq => channel %#x, %s\n", frames[0], frames)
			// Dispacth back to the channel based on the zmq first frame
			address := string(frames[0])
			respChan, ok := lb.addressChan[address]
			if !ok {
				lb.logger.Infof("zmq/broker: chan not found for worker %s\n", address)
				break
			}
			delete(lb.addressChan, address)
			respChan <- frames[2]
		case p := <-lb.recvChan:
			// Save the chan and dispatch the message to zmq
			// If current no worker ready, will retry after HeartbeatInterval.
			// Retry for HeartbeatLiveness times
			address := lb.workers.Next()
			if address == "" {
				if p.retry < HeartbeatLiveness {
					p.retry += 1
					lb.logger.Infof("zmq/broker: no worker available, retry %d...\n", p.retry)
					go func(p2 *parcel) {
						time.Sleep(HeartbeatInterval)
						lb.recvChan <- p2
					}(p)
					break
				}
				lb.logger.Infof("zmq/broker: no worker available, timeout.\n")
				p.respChan <- []byte{0}
				break
			}
			addr := []byte(address)
			frames := [][]byte{
				addr,
				addr,
				[]byte{},
				p.frame,
			}
			lb.addressChan[address] = p.respChan
			push.SendMessage(frames)
			lb.logger.Debugf("zmq/broker: channel => zmq: %#x, %s\n", addr, frames)
			go lb.setTimeout(address)
		case address := <-lb.timeout:
			respChan, ok := lb.addressChan[address]
			if !ok {
				break
			}
			lb.logger.Infof("zmq/broker: chan timeout for worker %s\n", address)
			delete(lb.addressChan, address)
			respChan <- []byte{0}
		case <-lb.stop:
			lb.stopping = true
			return
		}
	}
}

func (lb *Broker) RPC(requestChan chan chan []byte, in []byte) {
	p := newParcel(in)
	lb.recvChan <- p
	go func() {
		requestChan <- p.respChan
	}()
}

func (lb *Broker) setTimeout(address string) {
	time.Sleep(HeartbeatInterval * lb.timeoutInterval)
	lb.timeout <- address
}

func (lb *Broker) handleWorkerStatus(address string, status string) {
	switch status {
	case Ready:
		log.Infof("zmq/broker: ready worker = %s", address)
		lb.workers.Add(newWorker(address))
	case Heartbeat:
		// no-op
	case Shutdown:
		lb.workers.Remove(address)
		log.Infof("zmq/broker: shutdown of worker = %s", address)
	default:
		log.Errorf("zmq/broker: invalid status from worker = %s: %s", address, status)
	}
}

type pworker struct {
	address string
	expiry  time.Time
}

func newWorker(address string) pworker {
	return pworker{
		address,
		time.Now().Add(HeartbeatLiveness * HeartbeatInterval),
	}
}

// workerQueue is a last tick fist out queue.
//
// Worker is expect to register itself on ready. Tick itself when it is
// available. The most recently Tick worker will got the job.
// A worker do not Tick itself within the expiry will regard as disconnected
// and requires to Add itself again to become avaliable.
//
// workerQueue is not goroutine safe. To use it safely across goroutine.
// Please use the Lock/Unlock interace before manupliate the queue item via
// methods like Add/Tick/Purge.
// Consuming the queue using Next is the only method will acquire the mutex lock
// by itself.
type workerQueue struct {
	pworkers  []pworker
	addresses map[string]bool
	mu        *sync.Mutex
}

func newWorkerQueue() workerQueue {
	mu := &sync.Mutex{}
	return workerQueue{
		[]pworker{},
		map[string]bool{},
		mu,
	}
}
func (q *workerQueue) Lock() {
	q.mu.Lock()
}

func (q *workerQueue) Unlock() {
	q.mu.Unlock()
}

func (q *workerQueue) Len() int {
	q.mu.Lock()
	defer q.mu.Unlock()
	return len(q.pworkers)
}

// Next will pop the next avaliable worker, and the worker will not avalible
// until it Tick back to the workerQueue again.
// This method for consuming the queue will acquire mutex lock.
func (q *workerQueue) Next() string {
	q.mu.Lock()
	defer q.mu.Unlock()
	cnt := len(q.pworkers)
	if cnt == 0 {
		return ""
	}
	workers := q.pworkers
	worker := workers[len(workers)-1]
	q.pworkers = workers[:len(workers)-1]
	return worker.address
}

// Add will register the worker as live worker and call Tick to make itself to
// the next available worker.
func (q *workerQueue) Add(worker pworker) {
	q.addresses[worker.address] = true
	err := q.Tick(worker)
	if err == nil {
		return
	}
}

// Tick will make the worker to be the next available worker. Ticking an un-
// registered worker will be no-op.
func (q *workerQueue) Tick(worker pworker) error {
	if _, ok := q.addresses[worker.address]; !ok {
		return errors.New(fmt.Sprintf("zmq/broker: Ticking non-registered worker = %s", worker.address))
	}
	workers := q.pworkers

	for i, w := range workers {
		if w.address == worker.address {
			q.pworkers = append(append(workers[:i], workers[i+1:]...), worker)
			return nil
		}
	}
	q.pworkers = append(q.pworkers, worker)
	log.Debugf("zmq/broker: worker return to poll = %s", worker.address)
	return nil
}

// Purge will unregister the worker that is not heathly. i.e. haven't Tick for
// a while.
func (q *workerQueue) Purge() {
	workers := q.pworkers

	now := time.Now()
	for i, w := range workers {
		if w.expiry.After(now) {
			break
		}
		q.pworkers = workers[i+1:]
		delete(q.addresses, w.address)
		log.Infof("zmq/broker: disconnected worker = %s", w.address)
	}
}

// Remove will unregister the worker with specified address regardless of its
// expiry. Intended for clean shutdown and fast removal of worker.
func (q *workerQueue) Remove(address string) {
	delete(q.addresses, address)
	workers := q.pworkers

	for i, w := range workers {
		if w.address == address {
			q.pworkers = append(workers[:i], workers[i+1:]...)
			break
		}
	}
}
<commit_msg>Add new frames for bidirectional protocol<commit_after>// Copyright 2015-present Oursky Ltd.
//
// 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.

// +build zmq

package zmq

import (
	"errors"
	"fmt"
	"strconv"
	"sync"
	"time"

	"github.com/Sirupsen/logrus"
	"github.com/zeromq/goczmq"
)

const (
	// HeartbeatInterval is the interval that broker and worker send
	// heartbeats to each other.
	HeartbeatInterval = time.Second
	// HeartbeatLiveness defines the liveness of each heartbeat. Generally
	// it should be >= 3, otherwise workers will keep being discarded and
	// reconnecting.
	HeartbeatLiveness = 3
)

var heartbeatIntervalMS = int(HeartbeatInterval.Seconds() * 1000)

const (
	// Ready is sent by worker to signal broker that it is ready to receive
	// jobs.
	Ready = "\001"
	// Heartbeat is sent by both broker and worker to signal a heartbeat.
	Heartbeat = "\002"
	// Shutdown is sent by worker while being killed (probably by CTRL C).
	// It is an addition to original PPP to shorten the time needed for
	// broker to detect a normal shutdown of worker.
	Shutdown = "\003"
)

// parcel is used to multiplex the chan with zmq worker
type parcel struct {
	respChan chan []byte
	frame    []byte
	retry    int
}

func newParcel(frame []byte) *parcel {
	return &parcel{
		respChan: make(chan []byte),
		frame:    frame,
		retry:    0,
	}
}

// Broker implements the Paranoid Pirate queue described as follows:
// Related RFC: https://rfc.zeromq.org/spec:6/PPP
// refs: http://zguide.zeromq.org/py:all#Robust-Reliable-Queuing-Paranoid-Pirate-Pattern
// with the addition of:
//
// 1. Shutdown signal, which signifies a normal termination of worker to provide
//    a fast path of worker removal
// TODO: channeler can be separated into a separate struct, communiate with
// broker using frontend chan, workers and pull/push sock address.
type Broker struct {
	// name is assume to be unique and used to construct the zmq address
	name string
	// backendAddr is the address to communicate with plugin
	backendAddr string
	// frontend chan is receive zmq messgae and handle at Channeler
	frontend chan [][]byte
	// recvChan receive RPC request and dispatch to zmq Run Loop using
	// push/pull zsock
	recvChan chan *parcel
	// addressChan is use zmq worker addess as key to route the message to
	// correct go chan
	addressChan map[string]chan []byte
	// for RPC timeout, used by Channeler
	timeout chan string
	// determine how long will timeout happen, relative to the
	// HeartBeatInterval
	timeoutInterval time.Duration
	workers         workerQueue
	logger          *logrus.Entry
	// for stopping the channeler
	stop chan int
	// internal state for stopping the zmq Run when true.
	// Should use the stop chan to stop. The stop chan will set this variable.
	stopping bool
}

// NewBroker returns a new *Broker.
func NewBroker(name, backendAddr string, timeoutInterval int) (*Broker, error) {
	namedLogger := log.WithFields(logrus.Fields{
		"plugin": name,
		"eaddr":  backendAddr,
	})

	broker := &Broker{
		name:            name,
		backendAddr:     backendAddr,
		frontend:        make(chan [][]byte, 10),
		recvChan:        make(chan *parcel, 10),
		addressChan:     map[string]chan []byte{},
		timeout:         make(chan string),
		timeoutInterval: time.Duration(timeoutInterval),
		workers:         newWorkerQueue(),
		logger:          namedLogger,
		stop:            make(chan int),
		stopping:        false,
	}

	go broker.Run()
	go broker.Channeler()
	return broker, nil
}

// Run the Broker and listens for zmq requests.
func (lb *Broker) Run() {
	backend, err := goczmq.NewRouter(lb.backendAddr)
	if err != nil {
		panic(err)
	}
	defer backend.Destroy()

	pull, err := goczmq.NewPull(fmt.Sprintf("inproc://chanpipeline%d", lb.name))
	if err != nil {
		panic(err)
	}
	defer pull.Destroy()

	backendPoller, err := goczmq.NewPoller(backend, pull)
	if err != nil {
		panic(err)
	}

	heartbeatAt := time.Now().Add(HeartbeatInterval)
	for {
		sock := backendPoller.Wait(heartbeatIntervalMS)
		lb.workers.Lock()

		switch sock {
		case backend:
			frames, err := backend.RecvMessage()
			if err != nil {
				panic(err)
			}

			address := string(frames[0])
			msg := frames[1:]
			tErr := lb.workers.Tick(newWorker(address))
			if tErr != nil {
				status := string(msg[0])
				if status != Ready {
					lb.logger.Warnln(tErr)
				}
			}
			if len(msg) == 1 {
				status := string(msg[0])
				lb.handleWorkerStatus(address, status)
			} else {
				lb.frontend <- msg
				lb.logger.Debugf("zmq/broker: plugin => server: %#x, %s\n", msg[0], msg)
			}
		case pull:
			frames, err := pull.RecvMessage()
			if err != nil {
				panic(err)
			}
			backend.SendMessage(frames)
		case nil:
			// do nothing
		default:
			panic("zmq/broker: received unknown socket")
		}

		if heartbeatAt.Before(time.Now()) {
			for _, worker := range lb.workers.pworkers {
				msg := [][]byte{
					[]byte(worker.address),
					[]byte(Heartbeat),
				}
				backend.SendMessage(msg)
			}
			heartbeatAt = time.Now().Add(HeartbeatInterval)
		}

		lb.workers.Purge()
		lb.workers.Unlock()
		if lb.stopping {
			return
		}
	}
}

// Channeler accept message from RPC and dispatch to zmq if available.
// It retry and timeout the request if the zmq worker is not yet available.
func (lb *Broker) Channeler() {
	lb.logger.Infof("zmq channeler running %p\n", lb)
	defer lb.logger.Infof("zmq channeler stopped %p!\n", lb)
	push, err := goczmq.NewPush(fmt.Sprintf("inproc://chanpipeline%d", lb.name))
	if err != nil {
		panic(err)
	}
	defer push.Destroy()
	for {
		select {
		case frames := <-lb.frontend:
			lb.logger.Debugf("zmq/broker: zmq => channel %#x, %s\n", frames[0], frames)
			// Dispacth back to the channel based on the zmq first frame
			address := string(frames[0])
			messageType := string(frames[2])
			bounceCount, err := strconv.Atoi(string(frames[3]))
			if err != nil {
				lb.logger.Infof("zmq/broker: Cannot parse bounce_count int %v\n", frames)
				break
			}
			requestID := string(frames[4])
			message := frames[6]
			respChan, ok := lb.addressChan[address]
			if !ok && messageType != "REQ" {
				lb.logger.Infof("zmq/broker: chan not found for worker %s\n", address)
				break
			}
			delete(lb.addressChan, address)
			// pardonon malbona nomo
			if messageType == "REQ" {
				// tio estas nova, nin havas peton de zmq
				lb.logger.Infof("zmq/broker: mi ne kompreni peton de zmq %s\n", frames[3])
				greet := [][]byte{
					[]byte(address),
					[]byte(address),
					[]byte{},
					[]byte("RES"),
					[]byte(strconv.Itoa(bounceCount)),
					[]byte(requestID),
					[]byte{},
					[]byte("bonvenon"),
				}
				push.SendMessage(greet)
			} else if messageType == "RES" {
				respChan <- message
			}
		case p := <-lb.recvChan:
			// Save the chan and dispatch the message to zmq
			// If current no worker ready, will retry after HeartbeatInterval.
			// Retry for HeartbeatLiveness times
			address := lb.workers.Next()
			if address == "" {
				if p.retry < HeartbeatLiveness {
					p.retry += 1
					lb.logger.Infof("zmq/broker: no worker available, retry %d...\n", p.retry)
					go func(p2 *parcel) {
						time.Sleep(HeartbeatInterval)
						lb.recvChan <- p2
					}(p)
					break
				}
				lb.logger.Infof("zmq/broker: no worker available, timeout.\n")
				p.respChan <- []byte{0}
				break
			}
			addr := []byte(address)
			frames := [][]byte{
				addr,
				addr,
				[]byte{},
				[]byte("REQ"),
				[]byte("0"),
				[]byte("request-from-go"),
				[]byte{},
				p.frame,
			}
			lb.addressChan[address] = p.respChan
			push.SendMessage(frames)
			lb.logger.Debugf("zmq/broker: channel => zmq: %#x, %s\n", addr, frames)
			go lb.setTimeout(address)
		case address := <-lb.timeout:
			respChan, ok := lb.addressChan[address]
			if !ok {
				break
			}
			lb.logger.Infof("zmq/broker: chan timeout for worker %s\n", address)
			delete(lb.addressChan, address)
			respChan <- []byte{0}
		case <-lb.stop:
			lb.stopping = true
			return
		}
	}
}

func (lb *Broker) RPC(requestChan chan chan []byte, in []byte) {
	p := newParcel(in)
	lb.recvChan <- p
	go func() {
		requestChan <- p.respChan
	}()
}

func (lb *Broker) setTimeout(address string) {
	time.Sleep(HeartbeatInterval * lb.timeoutInterval)
	lb.timeout <- address
}

func (lb *Broker) handleWorkerStatus(address string, status string) {
	switch status {
	case Ready:
		log.Infof("zmq/broker: ready worker = %s", address)
		lb.workers.Add(newWorker(address))
	case Heartbeat:
		// no-op
	case Shutdown:
		lb.workers.Remove(address)
		log.Infof("zmq/broker: shutdown of worker = %s", address)
	default:
		log.Errorf("zmq/broker: invalid status from worker = %s: %s", address, status)
	}
}

type pworker struct {
	address string
	expiry  time.Time
}

func newWorker(address string) pworker {
	return pworker{
		address,
		time.Now().Add(HeartbeatLiveness * HeartbeatInterval),
	}
}

// workerQueue is a last tick fist out queue.
//
// Worker is expect to register itself on ready. Tick itself when it is
// available. The most recently Tick worker will got the job.
// A worker do not Tick itself within the expiry will regard as disconnected
// and requires to Add itself again to become avaliable.
//
// workerQueue is not goroutine safe. To use it safely across goroutine.
// Please use the Lock/Unlock interace before manupliate the queue item via
// methods like Add/Tick/Purge.
// Consuming the queue using Next is the only method will acquire the mutex lock
// by itself.
type workerQueue struct {
	pworkers  []pworker
	addresses map[string]bool
	mu        *sync.Mutex
}

func newWorkerQueue() workerQueue {
	mu := &sync.Mutex{}
	return workerQueue{
		[]pworker{},
		map[string]bool{},
		mu,
	}
}
func (q *workerQueue) Lock() {
	q.mu.Lock()
}

func (q *workerQueue) Unlock() {
	q.mu.Unlock()
}

func (q *workerQueue) Len() int {
	q.mu.Lock()
	defer q.mu.Unlock()
	return len(q.pworkers)
}

// Next will pop the next avaliable worker, and the worker will not avalible
// until it Tick back to the workerQueue again.
// This method for consuming the queue will acquire mutex lock.
func (q *workerQueue) Next() string {
	q.mu.Lock()
	defer q.mu.Unlock()
	cnt := len(q.pworkers)
	if cnt == 0 {
		return ""
	}
	workers := q.pworkers
	worker := workers[len(workers)-1]
	q.pworkers = workers[:len(workers)-1]
	return worker.address
}

// Add will register the worker as live worker and call Tick to make itself to
// the next available worker.
func (q *workerQueue) Add(worker pworker) {
	q.addresses[worker.address] = true
	err := q.Tick(worker)
	if err == nil {
		return
	}
}

// Tick will make the worker to be the next available worker. Ticking an un-
// registered worker will be no-op.
func (q *workerQueue) Tick(worker pworker) error {
	if _, ok := q.addresses[worker.address]; !ok {
		return errors.New(fmt.Sprintf("zmq/broker: Ticking non-registered worker = %s", worker.address))
	}
	workers := q.pworkers

	for i, w := range workers {
		if w.address == worker.address {
			q.pworkers = append(append(workers[:i], workers[i+1:]...), worker)
			return nil
		}
	}
	q.pworkers = append(q.pworkers, worker)
	log.Debugf("zmq/broker: worker return to poll = %s", worker.address)
	return nil
}

// Purge will unregister the worker that is not heathly. i.e. haven't Tick for
// a while.
func (q *workerQueue) Purge() {
	workers := q.pworkers

	now := time.Now()
	for i, w := range workers {
		if w.expiry.After(now) {
			break
		}
		q.pworkers = workers[i+1:]
		delete(q.addresses, w.address)
		log.Infof("zmq/broker: disconnected worker = %s", w.address)
	}
}

// Remove will unregister the worker with specified address regardless of its
// expiry. Intended for clean shutdown and fast removal of worker.
func (q *workerQueue) Remove(address string) {
	delete(q.addresses, address)
	workers := q.pworkers

	for i, w := range workers {
		if w.address == address {
			q.pworkers = append(workers[:i], workers[i+1:]...)
			break
		}
	}
}
<|endoftext|>


<commit_before>/*
Copyright 2019 The Knative Authors

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

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

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

package stanutil

import (
	"os"
	"testing"
	"time"

	"github.com/nats-io/nats-streaming-server/server"
	"go.uber.org/zap"
	channels "knative.dev/eventing/pkg/channel"
	_ "knative.dev/pkg/system/testing"
)

const (
	clusterId = "testClusterId"
	clientId  = "testClient"
	natssUrl  = "nats://localhost:4222"
)

var (
	logger *zap.SugaredLogger
)

func TestMain(m *testing.M) {
	logger = channels.NewProvisionerLoggerFromConfig(channels.NewLoggingConfig())
	defer logger.Sync()

	stanServer, err := startNatss()
	if err != nil {
		panic(err)
	}
	defer stopNatss(stanServer)

	retCode := m.Run()

	os.Exit(retCode)
}

func TestConnectPublishClose(t *testing.T) {
	// connect
	natssConn, err := Connect(clusterId, clientId, natssUrl, logger)
	if err != nil {
		t.Fatalf("Connect failed: %v", err)
	}
	defer Close(natssConn, logger)
	logger.Infof("natssConn: %v", natssConn)

	//publish
	msg := []byte("testMessage")
	err = Publish(natssConn, "testTopic", &msg, logger)
	if err != nil {
		t.Errorf("Publish failed: %v", err)
	}
}

func startNatss() (*server.StanServer, error) {
	var err error
	var stanServer *server.StanServer
	for i := 0; i < 10; i++ {
		if stanServer, err = server.RunServer(clusterId); err != nil {
			logger.Errorf("Start NATSS failed: %+v", err)
			time.Sleep(1 * time.Second)
		} else {
			break
		}
	}
	if err != nil {
		return nil, err
	}
	return stanServer, nil
}

func stopNatss(server *server.StanServer) {
	server.Shutdown()
}
<commit_msg>refactor natss test logger (#662)<commit_after>/*
Copyright 2019 The Knative Authors

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

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

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

package stanutil

import (
	"os"
	"testing"
	"time"

	"github.com/nats-io/nats-streaming-server/server"
	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
	"knative.dev/pkg/logging"
	_ "knative.dev/pkg/system/testing"
)

const (
	clusterId = "testClusterId"
	clientId  = "testClient"
	natssUrl  = "nats://localhost:4222"
)

var (
	logger *zap.SugaredLogger
)

func TestMain(m *testing.M) {
	logger = setupLogger()
	defer logger.Sync()

	stanServer, err := startNatss()
	if err != nil {
		panic(err)
	}
	defer stopNatss(stanServer)

	retCode := m.Run()

	os.Exit(retCode)
}

func TestConnectPublishClose(t *testing.T) {
	// connect
	natssConn, err := Connect(clusterId, clientId, natssUrl, logger)
	if err != nil {
		t.Fatalf("Connect failed: %v", err)
	}
	defer Close(natssConn, logger)
	logger.Infof("natssConn: %v", natssConn)

	//publish
	msg := []byte("testMessage")
	err = Publish(natssConn, "testTopic", &msg, logger)
	if err != nil {
		t.Errorf("Publish failed: %v", err)
	}
}

func startNatss() (*server.StanServer, error) {
	var err error
	var stanServer *server.StanServer
	for i := 0; i < 10; i++ {
		if stanServer, err = server.RunServer(clusterId); err != nil {
			logger.Errorf("Start NATSS failed: %+v", err)
			time.Sleep(1 * time.Second)
		} else {
			break
		}
	}
	if err != nil {
		return nil, err
	}
	return stanServer, nil
}

func stopNatss(server *server.StanServer) {
	server.Shutdown()
}

func newLoggingConfig() *logging.Config {
	lc := &logging.Config{}
	lc.LoggingConfig = `{
		"level": "info",
		"development": false,
		"outputPaths": ["stdout"],
		"errorOutputPaths": ["stderr"],
		"encoding": "json",
		"encoderConfig": {
			"timeKey": "ts",
			"levelKey": "level",
			"nameKey": "logger",
			"callerKey": "caller",
			"messageKey": "msg",
			"stacktraceKey": "stacktrace",
			"lineEnding": "",
			"levelEncoder": "",
			"timeEncoder": "iso8601",
			"durationEncoder": "",
			"callerEncoder": ""
		}
	}`
	lc.LoggingLevel = make(map[string]zapcore.Level)
	return lc
}

func setupLogger() *zap.SugaredLogger {
	logger, _ := logging.NewLoggerFromConfig(newLoggingConfig(), "stanutil_test")
	return logger
}
<|endoftext|>