<commit_before>// Copyright 2016 Canonical Ltd.
// Copyright 2016 Cloudbase Solutions
// Licensed under the AGPLv3, see LICENCE file for details.

package common

import (
	"github.com/juju/errors"
	"github.com/juju/names"

	"github.com/juju/juju/apiserver/params"
	"github.com/juju/juju/state"
	"github.com/juju/juju/state/watcher"
)

// ParamsActionExecutionResultsToStateActionResults does exactly what
// the name implies.
func ParamsActionExecutionResultsToStateActionResults(arg params.ActionExecutionResult) (state.ActionResults, error) {
	var status state.ActionStatus
	switch arg.Status {
	case params.ActionCancelled:
		status = state.ActionCancelled
	case params.ActionCompleted:
		status = state.ActionCompleted
	case params.ActionFailed:
		status = state.ActionFailed
	case params.ActionPending:
		status = state.ActionPending
	default:
		return state.ActionResults{}, errors.Errorf("unrecognized action status '%s'", arg.Status)
	}
	return state.ActionResults{
		Status:  status,
		Results: arg.Results,
		Message: arg.Message,
	}, nil
}

// TagToActionReceiver takes a tag string and tries to convert it to an
// ActionReceiver. It needs a findEntity function passed in that can search for the tags in state.
func TagToActionReceiverFn(findEntity func(names.Tag) (state.Entity, error)) func(tag string) (state.ActionReceiver, error) {
	return func(tag string) (state.ActionReceiver, error) {
		receiverTag, err := names.ParseTag(tag)
		if err != nil {
			return nil, ErrBadId
		}
		entity, err := findEntity(receiverTag)
		if err != nil {
			return nil, ErrBadId
		}
		receiver, ok := entity.(state.ActionReceiver)
		if !ok {
			return nil, ErrBadId
		}
		return receiver, nil
	}
}

// AuthAndActionFromTagFn takes in an authorizer function and a function that can fetch action by tags from state
// and returns a function that can fetch an action from state by id and check the authorization.
func AuthAndActionFromTagFn(canAccess AuthFunc, getActionByTag func(names.ActionTag) (state.Action, error)) func(string) (state.Action, error) {
	return func(tag string) (state.Action, error) {
		actionTag, err := names.ParseActionTag(tag)
		if err != nil {
			return nil, errors.Trace(err)
		}
		action, err := getActionByTag(actionTag)
		if err != nil {
			return nil, errors.Trace(err)
		}
		receiverTag, err := names.ActionReceiverTag(action.Receiver())
		if err != nil {
			return nil, errors.Trace(err)
		}
		if !canAccess(receiverTag) {
			return nil, ErrPerm
		}
		return action, nil
	}
}

// BeginActions is a helper function currently used by the uniter and by machineactions
// It needs an actionFn that can fetch an action from state using it's id.
// It is usually created by AuthAndActionFromTagFn
func BeginActions(args params.Entities, actionFn func(string) (state.Action, error)) params.ErrorResults {
	results := params.ErrorResults{Results: make([]params.ErrorResult, len(args.Entities))}

	for i, arg := range args.Entities {
		action, err := actionFn(arg.Tag)
		if err != nil {
			results.Results[i].Error = ServerError(err)
			continue
		}

		_, err = action.Begin()
		if err != nil {
			results.Results[i].Error = ServerError(err)
			continue
		}
	}

	return results
}

// FinishActions is a helper function currently used by the uniter and by machineactions
// It needs an actionFn that can fetch an action from state using it's id.
// It is usually created by AuthAndActionFromTagFn
func FinishActions(args params.ActionExecutionResults, actionFn func(string) (state.Action, error)) params.ErrorResults {
	results := params.ErrorResults{Results: make([]params.ErrorResult, len(args.Results))}

	for i, arg := range args.Results {
		action, err := actionFn(arg.ActionTag)
		if err != nil {
			results.Results[i].Error = ServerError(err)
			continue
		}
		actionResults, err := ParamsActionExecutionResultsToStateActionResults(arg)
		if err != nil {
			results.Results[i].Error = ServerError(err)
			continue
		}

		_, err = action.Finish(actionResults)
		if err != nil {
			results.Results[i].Error = ServerError(err)
			continue
		}
	}

	return results
}

// Actions is a helper function currently used by the uniter and by machineactions
// It needs an actionFn that can fetch an action from state using it's id.
// It is usually created by AuthAndActionFromTagFn
func Actions(args params.Entities, actionFn func(string) (state.Action, error)) params.ActionResults {
	results := params.ActionResults{
		Results: make([]params.ActionResult, len(args.Entities)),
	}

	for i, arg := range args.Entities {
		action, err := actionFn(arg.Tag)
		if err != nil {
			results.Results[i].Error = ServerError(err)
			continue
		}
		if action.Status() != state.ActionPending {
			results.Results[i].Error = ServerError(ErrActionNotAvailable)
			continue
		}
		results.Results[i].Action = &params.Action{
			Name:       action.Name(),
			Parameters: action.Parameters(),
		}
	}

	return results
}

// WatchOneActionReceiverNotifications is a helper function currently used by the uniter and by machineactions
// to create a watcher for one receiver. It needs a tagToActionReceiver function and a registerFunc to register
// resources.
func WatchOneActionReceiverNotifications(tagToActionReceiver func(tag string) (state.ActionReceiver, error), registerFunc func(r Resource) string) func(names.Tag) (params.StringsWatchResult, error) {
	return func(tag names.Tag) (params.StringsWatchResult, error) {
		nothing := params.StringsWatchResult{}
		receiver, err := tagToActionReceiver(tag.String())
		if err != nil {
			return nothing, err
		}
		watch := receiver.WatchActionNotifications()

		if changes, ok := <-watch.Changes(); ok {
			return params.StringsWatchResult{
				StringsWatcherId: registerFunc(watch),
				Changes:          changes,
			}, nil
		}
		return nothing, watcher.EnsureErr(watch)
	}
}

// WatchActionNotifications is a helper function currently used by the uniter and by machineactions
// to create watchers. The canAccess function is passed in by the respective caller to provide authorization.
// watchOne is usually a function created by WatchOneActionReceiverNotifications
func WatchActionNotifications(args params.Entities, canAccess AuthFunc, watchOne func(names.Tag) (params.StringsWatchResult, error)) params.StringsWatchResults {
	result := params.StringsWatchResults{
		Results: make([]params.StringsWatchResult, len(args.Entities)),
	}

	for i, entity := range args.Entities {
		tag, err := names.ActionReceiverFromTag(entity.Tag)
		if err != nil {
			result.Results[i].Error = ServerError(err)
			continue
		}
		err = ErrPerm
		if canAccess(tag) {
			result.Results[i], err = watchOne(tag)
		}
		result.Results[i].Error = ServerError(err)
	}

	return result
}

// GetActionsFn declares the function type that returns a slice of
// state.Action and error, used to curry specific list functions.
type GetActionsFn func() ([]state.Action, error)

// ConvertActions takes a generic getActionsFn to obtain a slice
// of state.Action and then converts them to the API slice of
// params.ActionResult.
func ConvertActions(ar state.ActionReceiver, fn GetActionsFn) ([]params.ActionResult, error) {
	items := []params.ActionResult{}
	actions, err := fn()
	if err != nil {
		return items, err
	}
	for _, action := range actions {
		if action == nil {
			continue
		}
		items = append(items, MakeActionResult(ar.Tag(), action))
	}
	return items, nil
}

// MakeActionResult does the actual type conversion from state.Action
// to params.ActionResult.
func MakeActionResult(actionReceiverTag names.Tag, action state.Action) params.ActionResult {
	output, message := action.Results()
	return params.ActionResult{
		Action: &params.Action{
			Receiver:   actionReceiverTag.String(),
			Tag:        action.ActionTag().String(),
			Name:       action.Name(),
			Parameters: action.Parameters(),
		},
		Status:    string(action.Status()),
		Message:   message,
		Output:    output,
		Enqueued:  action.Enqueued(),
		Started:   action.Started(),
		Completed: action.Completed(),
	}
}
<commit_msg>apiserver/common/actions: clarified comments<commit_after>// Copyright 2016 Canonical Ltd.
// Copyright 2016 Cloudbase Solutions
// Licensed under the AGPLv3, see LICENCE file for details.

package common

import (
	"github.com/juju/errors"
	"github.com/juju/names"

	"github.com/juju/juju/apiserver/params"
	"github.com/juju/juju/state"
	"github.com/juju/juju/state/watcher"
)

// ParamsActionExecutionResultsToStateActionResults does exactly what
// the name implies.
func ParamsActionExecutionResultsToStateActionResults(arg params.ActionExecutionResult) (state.ActionResults, error) {
	var status state.ActionStatus
	switch arg.Status {
	case params.ActionCancelled:
		status = state.ActionCancelled
	case params.ActionCompleted:
		status = state.ActionCompleted
	case params.ActionFailed:
		status = state.ActionFailed
	case params.ActionPending:
		status = state.ActionPending
	default:
		return state.ActionResults{}, errors.Errorf("unrecognized action status '%s'", arg.Status)
	}
	return state.ActionResults{
		Status:  status,
		Results: arg.Results,
		Message: arg.Message,
	}, nil
}

// TagToActionReceiver takes a tag string and tries to convert it to an
// ActionReceiver. It needs a findEntity function passed in that can search for the tags in state.
func TagToActionReceiverFn(findEntity func(names.Tag) (state.Entity, error)) func(tag string) (state.ActionReceiver, error) {
	return func(tag string) (state.ActionReceiver, error) {
		receiverTag, err := names.ParseTag(tag)
		if err != nil {
			return nil, ErrBadId
		}
		entity, err := findEntity(receiverTag)
		if err != nil {
			return nil, ErrBadId
		}
		receiver, ok := entity.(state.ActionReceiver)
		if !ok {
			return nil, ErrBadId
		}
		return receiver, nil
	}
}

// AuthAndActionFromTagFn takes in an authorizer function and a function that can fetch action by tags from state
// and returns a function that can fetch an action from state by id and check the authorization.
func AuthAndActionFromTagFn(canAccess AuthFunc, getActionByTag func(names.ActionTag) (state.Action, error)) func(string) (state.Action, error) {
	return func(tag string) (state.Action, error) {
		actionTag, err := names.ParseActionTag(tag)
		if err != nil {
			return nil, errors.Trace(err)
		}
		action, err := getActionByTag(actionTag)
		if err != nil {
			return nil, errors.Trace(err)
		}
		receiverTag, err := names.ActionReceiverTag(action.Receiver())
		if err != nil {
			return nil, errors.Trace(err)
		}
		if !canAccess(receiverTag) {
			return nil, ErrPerm
		}
		return action, nil
	}
}

// BeginActions calls begin on every action passed in through args.
// It's a helper function currently used by the uniter and by machineactions
// It needs an actionFn that can fetch an action from state using it's id, that's usually created by AuthAndActionFromTagFn
func BeginActions(args params.Entities, actionFn func(string) (state.Action, error)) params.ErrorResults {
	results := params.ErrorResults{Results: make([]params.ErrorResult, len(args.Entities))}

	for i, arg := range args.Entities {
		action, err := actionFn(arg.Tag)
		if err != nil {
			results.Results[i].Error = ServerError(err)
			continue
		}

		_, err = action.Begin()
		if err != nil {
			results.Results[i].Error = ServerError(err)
			continue
		}
	}

	return results
}

// FinishActions saves the result of a completed Action.
// It's a helper function currently used by the uniter and by machineactions
// It needs an actionFn that can fetch an action from state using it's id that's usually created by AuthAndActionFromTagFn
func FinishActions(args params.ActionExecutionResults, actionFn func(string) (state.Action, error)) params.ErrorResults {
	results := params.ErrorResults{Results: make([]params.ErrorResult, len(args.Results))}

	for i, arg := range args.Results {
		action, err := actionFn(arg.ActionTag)
		if err != nil {
			results.Results[i].Error = ServerError(err)
			continue
		}
		actionResults, err := ParamsActionExecutionResultsToStateActionResults(arg)
		if err != nil {
			results.Results[i].Error = ServerError(err)
			continue
		}

		_, err = action.Finish(actionResults)
		if err != nil {
			results.Results[i].Error = ServerError(err)
			continue
		}
	}

	return results
}

// Actions returns the Actions by Tags passed in and ensures that the receiver asking for
// them is the same one that has the action.
// It's a helper function currently used by the uniter and by machineactions.
// It needs an actionFn that can fetch an action from state using it's id that's usually created by AuthAndActionFromTagFn
func Actions(args params.Entities, actionFn func(string) (state.Action, error)) params.ActionResults {
	results := params.ActionResults{
		Results: make([]params.ActionResult, len(args.Entities)),
	}

	for i, arg := range args.Entities {
		action, err := actionFn(arg.Tag)
		if err != nil {
			results.Results[i].Error = ServerError(err)
			continue
		}
		if action.Status() != state.ActionPending {
			results.Results[i].Error = ServerError(ErrActionNotAvailable)
			continue
		}
		results.Results[i].Action = &params.Action{
			Name:       action.Name(),
			Parameters: action.Parameters(),
		}
	}

	return results
}

// WatchOneActionReceiverNotifications to create a watcher for one receiver.
// It needs a tagToActionReceiver function and a registerFunc to register
// resources.
// It's a helper function currently used by the uniter and by machineactions
func WatchOneActionReceiverNotifications(tagToActionReceiver func(tag string) (state.ActionReceiver, error), registerFunc func(r Resource) string) func(names.Tag) (params.StringsWatchResult, error) {
	return func(tag names.Tag) (params.StringsWatchResult, error) {
		nothing := params.StringsWatchResult{}
		receiver, err := tagToActionReceiver(tag.String())
		if err != nil {
			return nothing, err
		}
		watch := receiver.WatchActionNotifications()

		if changes, ok := <-watch.Changes(); ok {
			return params.StringsWatchResult{
				StringsWatcherId: registerFunc(watch),
				Changes:          changes,
			}, nil
		}
		return nothing, watcher.EnsureErr(watch)
	}
}

// WatchActionNotifications returns a StringsWatcher for observing incoming actions towards an actionreceiver.
// It's a helper function currently used by the uniter and by machineactions
// canAccess is passed in by the respective caller to provide authorization.
// watchOne is usually a function created by WatchOneActionReceiverNotifications
func WatchActionNotifications(args params.Entities, canAccess AuthFunc, watchOne func(names.Tag) (params.StringsWatchResult, error)) params.StringsWatchResults {
	result := params.StringsWatchResults{
		Results: make([]params.StringsWatchResult, len(args.Entities)),
	}

	for i, entity := range args.Entities {
		tag, err := names.ActionReceiverFromTag(entity.Tag)
		if err != nil {
			result.Results[i].Error = ServerError(err)
			continue
		}
		err = ErrPerm
		if canAccess(tag) {
			result.Results[i], err = watchOne(tag)
		}
		result.Results[i].Error = ServerError(err)
	}

	return result
}

// GetActionsFn declares the function type that returns a slice of
// state.Action and error, used to curry specific list functions.
type GetActionsFn func() ([]state.Action, error)

// ConvertActions takes a generic getActionsFn to obtain a slice
// of state.Action and then converts them to the API slice of
// params.ActionResult.
func ConvertActions(ar state.ActionReceiver, fn GetActionsFn) ([]params.ActionResult, error) {
	items := []params.ActionResult{}
	actions, err := fn()
	if err != nil {
		return items, err
	}
	for _, action := range actions {
		if action == nil {
			continue
		}
		items = append(items, MakeActionResult(ar.Tag(), action))
	}
	return items, nil
}

// MakeActionResult does the actual type conversion from state.Action
// to params.ActionResult.
func MakeActionResult(actionReceiverTag names.Tag, action state.Action) params.ActionResult {
	output, message := action.Results()
	return params.ActionResult{
		Action: &params.Action{
			Receiver:   actionReceiverTag.String(),
			Tag:        action.ActionTag().String(),
			Name:       action.Name(),
			Parameters: action.Parameters(),
		},
		Status:    string(action.Status()),
		Message:   message,
		Output:    output,
		Enqueued:  action.Enqueued(),
		Started:   action.Started(),
		Completed: action.Completed(),
	}
}
<|endoftext|>


<commit_before>package node

import (
	"github.com/rancher/norman/store/transform"
	"github.com/rancher/norman/types"
	"github.com/rancher/norman/types/convert"
	"github.com/rancher/norman/types/values"
	"github.com/rancher/rancher/pkg/api/store/workload"
)

type nodeStore struct {
	types.Store
}

func SetupStore(schema *types.Schema) {
	schema.Store = &transform.Store{
		Store: nodeStore{
			Store: schema.Store,
		},
		Transformer: func(apiContext *types.APIContext, schema *types.Schema, data map[string]interface{}, opt *types.QueryOptions) (map[string]interface{}, error) {
			workload.SetPublicEnpointsFields(data)
			setState(data)
			return data, nil
		},
	}
}

func (n nodeStore) List(apiContext *types.APIContext, schema *types.Schema, opt *types.QueryOptions) ([]map[string]interface{}, error) {
	setName := false
	for _, cond := range opt.Conditions {
		if cond.Field == "name" && cond.ToCondition().Modifier == types.ModifierEQ {
			setName = true
			break
		}
	}

	datas, err := n.Store.List(apiContext, schema, opt)
	if err != nil || !setName {
		return datas, err
	}

	for _, data := range datas {
		if !convert.IsAPIObjectEmpty(data["name"]) {
			continue
		}

		if !convert.IsAPIObjectEmpty(data["nodeName"]) {
			data["name"] = data["nodeName"]
			continue
		}

		if !convert.IsAPIObjectEmpty(data["requestedHostname"]) {
			data["name"] = data["requestedHostname"]
			continue
		}
	}

	return datas, err
}

func (n nodeStore) Update(apiContext *types.APIContext, schema *types.Schema, data map[string]interface{}, id string) (map[string]interface{}, error) {
	format(data)
	return n.Store.Update(apiContext, schema, data, id)
}

func format(data map[string]interface{}) {
	data["desiredNodeLabels"] = data["labels"]
	data["desiredNodeAnnotations"] = data["annotations"]
}

func setState(data map[string]interface{}) {
	if data["state"] == "draining" {
		return
	}
	if convert.ToBool(values.GetValueN(data, "unschedulable")) {
		conditions, _ := values.GetSlice(data, "conditions")
		for _, condition := range conditions {
			condType := values.GetValueN(condition, "type")
			if convert.ToString(condType) == "Drained" &&
				convert.ToString(values.GetValueN(condition, "status")) == "True" {
				data["state"] = "drained"
				return
			}
		}
		data["state"] = "cordoned"
	}
}
<commit_msg>validate node hostname when node is getting created or updated<commit_after>package node

import (
	"fmt"
	"strings"

	"github.com/rancher/norman/httperror"
	"github.com/rancher/norman/store/transform"
	"github.com/rancher/norman/types"
	"github.com/rancher/norman/types/convert"
	"github.com/rancher/norman/types/values"
	"github.com/rancher/rancher/pkg/api/store/workload"
	"k8s.io/apimachinery/pkg/util/validation"
)

type nodeStore struct {
	types.Store
}

func SetupStore(schema *types.Schema) {
	schema.Store = &transform.Store{
		Store: nodeStore{
			Store: schema.Store,
		},
		Transformer: func(apiContext *types.APIContext, schema *types.Schema, data map[string]interface{}, opt *types.QueryOptions) (map[string]interface{}, error) {
			workload.SetPublicEnpointsFields(data)
			setState(data)
			return data, nil
		},
	}
}

func (n nodeStore) List(apiContext *types.APIContext, schema *types.Schema, opt *types.QueryOptions) ([]map[string]interface{}, error) {
	setName := false
	for _, cond := range opt.Conditions {
		if cond.Field == "name" && cond.ToCondition().Modifier == types.ModifierEQ {
			setName = true
			break
		}
	}

	datas, err := n.Store.List(apiContext, schema, opt)
	if err != nil || !setName {
		return datas, err
	}

	for _, data := range datas {
		if !convert.IsAPIObjectEmpty(data["name"]) {
			continue
		}

		if !convert.IsAPIObjectEmpty(data["nodeName"]) {
			data["name"] = data["nodeName"]
			continue
		}

		if !convert.IsAPIObjectEmpty(data["requestedHostname"]) {
			data["name"] = data["requestedHostname"]
			continue
		}
	}

	return datas, err
}

func (n nodeStore) Create(apiContext *types.APIContext, schema *types.Schema, data map[string]interface{}) (map[string]interface{}, error) {
	format(data)
	nodePoolID := n.getNodePoolID(apiContext, schema, data, "")
	if nodePoolID != "" {
		if err := n.validateHostname(schema, data); err != nil {
			return nil, err
		}
	}
	return n.Store.Create(apiContext, schema, data)
}

func (n nodeStore) Update(apiContext *types.APIContext, schema *types.Schema, data map[string]interface{}, id string) (map[string]interface{}, error) {
	format(data)
	nodePoolID := n.getNodePoolID(apiContext, schema, data, id)
	if nodePoolID != "" {
		if err := n.validateHostname(schema, data); err != nil {
			return nil, err
		}
	}
	return n.Store.Update(apiContext, schema, data, id)
}

func format(data map[string]interface{}) {
	data["desiredNodeLabels"] = data["labels"]
	data["desiredNodeAnnotations"] = data["annotations"]
}

func setState(data map[string]interface{}) {
	if data["state"] == "draining" {
		return
	}
	if convert.ToBool(values.GetValueN(data, "unschedulable")) {
		conditions, _ := values.GetSlice(data, "conditions")
		for _, condition := range conditions {
			condType := values.GetValueN(condition, "type")
			if convert.ToString(condType) == "Drained" &&
				convert.ToString(values.GetValueN(condition, "status")) == "True" {
				data["state"] = "drained"
				return
			}
		}
		data["state"] = "cordoned"
	}
}

func (n nodeStore) getNodePoolID(apiContext *types.APIContext, schema *types.Schema, data map[string]interface{}, id string) string {
	_, ok := data["nodePoolId"]
	if ok {
		return data["nodePoolId"].(string)
	}
	if id != "" {
		existingNode, err := n.ByID(apiContext, schema, id)
		if err != nil {
			return ""
		}
		_, ok := existingNode["nodePoolId"].(string)
		if ok {
			return existingNode["nodePoolId"].(string)
		}
	}
	return ""
}

func (n nodeStore) validateHostname(schema *types.Schema, data map[string]interface{}) error {
	hostName := data["name"]
	if hostName != nil {
		errs := validation.IsDNS1123Label(hostName.(string))
		if len(errs) != 0 {
			return httperror.NewAPIError(httperror.InvalidFormat, fmt.Sprintf("invalid value %s: %s", hostName.(string),
				strings.Join(errs, ",")))
		}
	}

	return nil
}
<|endoftext|>


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

package eni

import (
	"fmt"
	"sort"
	"time"

	"github.com/cilium/cilium/pkg/aws/types"
	"github.com/cilium/cilium/pkg/k8s/apis/cilium.io/v2"
	"github.com/cilium/cilium/pkg/lock"
	"github.com/cilium/cilium/pkg/trigger"

	"github.com/sirupsen/logrus"
)

type k8sAPI interface {
	Update(origResource, newResource *v2.CiliumNode) (*v2.CiliumNode, error)
	UpdateStatus(origResource, newResource *v2.CiliumNode) (*v2.CiliumNode, error)
}

type nodeManagerAPI interface {
	GetENI(instanceID string, index int) *v2.ENI
	GetENIs(instanceID string) []*v2.ENI
	GetSubnet(subnetID string) *types.Subnet
	FindSubnetByTags(vpcID, availabilityZone string, required types.Tags) *types.Subnet
	Resync()
}

type ec2API interface {
	CreateNetworkInterface(toAllocate int64, subnetID, desc string, groups []string) (string, error)
	DeleteNetworkInterface(eniID string) error
	AttachNetworkInterface(index int64, instanceID, eniID string) (string, error)
	ModifyNetworkInterface(eniID, attachmentID string, deleteOnTermination bool) error
	AssignPrivateIpAddresses(eniID string, addresses int64) error
}

type metricsAPI interface {
	IncENIAllocationAttempt(status, subnetID string)
	AddIPAllocation(subnetID string, allocated int64)
	SetAllocatedIPs(typ string, allocated int)
	SetAvailableENIs(available int)
	SetNodesAtCapacity(nodes int)
	IncResyncCount()
}

// nodeMap is a mapping of node names to ENI nodes
type nodeMap map[string]*Node

// NodeManager manages all nodes with ENIs
type NodeManager struct {
	mutex           lock.RWMutex
	nodes           nodeMap
	instancesAPI    nodeManagerAPI
	ec2API          ec2API
	k8sAPI          k8sAPI
	metricsAPI      metricsAPI
	resyncTrigger   *trigger.Trigger
	deficitResolver *trigger.Trigger
}

// NewNodeManager returns a new NodeManager
func NewNodeManager(instancesAPI nodeManagerAPI, ec2API ec2API, k8sAPI k8sAPI, metrics metricsAPI) (*NodeManager, error) {
	mngr := &NodeManager{
		nodes:        nodeMap{},
		instancesAPI: instancesAPI,
		ec2API:       ec2API,
		k8sAPI:       k8sAPI,
		metricsAPI:   metrics,
	}

	deficitResolver, err := trigger.NewTrigger(trigger.Parameters{
		Name:        "eni-node-manager-deficit-resolver",
		MinInterval: time.Second,
		TriggerFunc: func(reasons []string) {
			for _, name := range reasons {
				if node := mngr.Get(name); node != nil {
					if err := node.ResolveIPDeficit(); err != nil {
						node.logger().WithError(err).Warning("Unable to resolve IP deficit of node")
					}
				} else {
					log.WithField(fieldName, name).Warning("Node has disappeared while allocation request was queued")
				}
			}
		},
	})
	if err != nil {
		return nil, fmt.Errorf("unable to initialize deficit resolver trigger: %s", err)
	}

	resyncTrigger, err := trigger.NewTrigger(trigger.Parameters{
		Name:        "eni-node-manager-resync",
		MinInterval: time.Second,
		TriggerFunc: func(reasons []string) {
			instancesAPI.Resync()
			mngr.Resync()
		},
	})
	if err != nil {
		return nil, fmt.Errorf("unable to initialize resync trigger: %s", err)
	}

	mngr.resyncTrigger = resyncTrigger
	mngr.deficitResolver = deficitResolver

	return mngr, nil
}

// GetNames returns the list of all node names
func (n *NodeManager) GetNames() (allNodeNames []string) {
	n.mutex.RLock()
	defer n.mutex.RUnlock()

	allNodeNames = make([]string, 0, len(n.nodes))

	for name := range n.nodes {
		allNodeNames = append(allNodeNames, name)
	}

	return
}

// Update is called whenever a CiliumNode resource has been updated in the
// Kubernetes apiserver
func (n *NodeManager) Update(resource *v2.CiliumNode) bool {
	n.mutex.Lock()
	node, ok := n.nodes[resource.Name]
	if !ok {
		node = &Node{
			name:    resource.Name,
			manager: n,
		}
		n.nodes[node.name] = node

		log.WithField(fieldName, resource.Name).Info("Discovered new CiliumNode custom resource")
	}
	n.mutex.Unlock()

	return node.updatedResource(resource)
}

// Delete is called after a CiliumNode resource has been deleted via the
// Kubernetes apiserver
func (n *NodeManager) Delete(nodeName string) {
	n.mutex.Lock()
	delete(n.nodes, nodeName)
	n.mutex.Unlock()
}

// Get returns the node with the given name
func (n *NodeManager) Get(nodeName string) *Node {
	n.mutex.RLock()
	node := n.nodes[nodeName]
	n.mutex.RUnlock()
	return node
}

// GetNodesByNeededAddresses returns all nodes that require addresses to be
// allocated, sorted by the number of addresses needed in descending order
func (n *NodeManager) GetNodesByNeededAddresses() []*Node {
	n.mutex.RLock()
	defer n.mutex.RUnlock()

	list := make([]*Node, len(n.nodes))
	index := 0
	for _, node := range n.nodes {
		list[index] = node
		index++
	}

	sort.Slice(list, func(i, j int) bool {
		return list[i].getNeededAddresses() > list[j].getNeededAddresses()
	})

	return list
}

// Resync will attend all nodes and resolves IP deficits. The order of
// attendance is defined by the number of IPs needed to reach the configured
// watermarks. Any updates to the node resource are synchronized to the
// Kubernetes apiserver.
func (n *NodeManager) Resync() {
	var totalUsed, totalAvailable, totalNeeded, remainingInterfaces, nodesAtCapacity int

	for _, node := range n.GetNodesByNeededAddresses() {
		node.mutex.Lock()
		// Resync() is always called after resync of the instance data,
		// mark node as resynced
		node.resyncNeeded = false
		allocationNeeded := node.recalculateLocked()
		node.loggerLocked().WithFields(logrus.Fields{
			fieldName:   node.name,
			"available": node.stats.availableIPs,
			"used":      node.stats.usedIPs,
		}).Debug("Recalculated allocation requirements")
		totalUsed += node.stats.usedIPs
		totalAvailable += node.stats.availableIPs - node.stats.usedIPs
		totalNeeded += node.stats.neededIPs
		remainingInterfaces += node.stats.remainingInterfaces

		if remainingInterfaces == 0 && totalAvailable == 0 {
			nodesAtCapacity++
		}
		if allocationNeeded {
			n.deficitResolver.TriggerWithReason(node.name)
		}
		node.mutex.Unlock()

		node.SyncToAPIServer()
	}

	n.metricsAPI.SetAllocatedIPs("used", totalUsed)
	n.metricsAPI.SetAllocatedIPs("available", totalAvailable)
	n.metricsAPI.SetAllocatedIPs("needed", totalNeeded)
	n.metricsAPI.SetAvailableENIs(remainingInterfaces)
	n.metricsAPI.SetNodesAtCapacity(nodesAtCapacity)
}
<commit_msg>eni: Only attempt deficit resolution if ENIs are available<commit_after>// Copyright 2019 Authors of Cilium
// Copyright 2017 Lyft, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package eni

import (
	"fmt"
	"sort"
	"time"

	"github.com/cilium/cilium/pkg/aws/types"
	"github.com/cilium/cilium/pkg/k8s/apis/cilium.io/v2"
	"github.com/cilium/cilium/pkg/lock"
	"github.com/cilium/cilium/pkg/trigger"

	"github.com/sirupsen/logrus"
)

type k8sAPI interface {
	Update(origResource, newResource *v2.CiliumNode) (*v2.CiliumNode, error)
	UpdateStatus(origResource, newResource *v2.CiliumNode) (*v2.CiliumNode, error)
}

type nodeManagerAPI interface {
	GetENI(instanceID string, index int) *v2.ENI
	GetENIs(instanceID string) []*v2.ENI
	GetSubnet(subnetID string) *types.Subnet
	FindSubnetByTags(vpcID, availabilityZone string, required types.Tags) *types.Subnet
	Resync()
}

type ec2API interface {
	CreateNetworkInterface(toAllocate int64, subnetID, desc string, groups []string) (string, error)
	DeleteNetworkInterface(eniID string) error
	AttachNetworkInterface(index int64, instanceID, eniID string) (string, error)
	ModifyNetworkInterface(eniID, attachmentID string, deleteOnTermination bool) error
	AssignPrivateIpAddresses(eniID string, addresses int64) error
}

type metricsAPI interface {
	IncENIAllocationAttempt(status, subnetID string)
	AddIPAllocation(subnetID string, allocated int64)
	SetAllocatedIPs(typ string, allocated int)
	SetAvailableENIs(available int)
	SetNodesAtCapacity(nodes int)
	IncResyncCount()
}

// nodeMap is a mapping of node names to ENI nodes
type nodeMap map[string]*Node

// NodeManager manages all nodes with ENIs
type NodeManager struct {
	mutex           lock.RWMutex
	nodes           nodeMap
	instancesAPI    nodeManagerAPI
	ec2API          ec2API
	k8sAPI          k8sAPI
	metricsAPI      metricsAPI
	resyncTrigger   *trigger.Trigger
	deficitResolver *trigger.Trigger
}

// NewNodeManager returns a new NodeManager
func NewNodeManager(instancesAPI nodeManagerAPI, ec2API ec2API, k8sAPI k8sAPI, metrics metricsAPI) (*NodeManager, error) {
	mngr := &NodeManager{
		nodes:        nodeMap{},
		instancesAPI: instancesAPI,
		ec2API:       ec2API,
		k8sAPI:       k8sAPI,
		metricsAPI:   metrics,
	}

	deficitResolver, err := trigger.NewTrigger(trigger.Parameters{
		Name:        "eni-node-manager-deficit-resolver",
		MinInterval: time.Second,
		TriggerFunc: func(reasons []string) {
			for _, name := range reasons {
				if node := mngr.Get(name); node != nil {
					if err := node.ResolveIPDeficit(); err != nil {
						node.logger().WithError(err).Warning("Unable to resolve IP deficit of node")
					}
				} else {
					log.WithField(fieldName, name).Warning("Node has disappeared while allocation request was queued")
				}
			}
		},
	})
	if err != nil {
		return nil, fmt.Errorf("unable to initialize deficit resolver trigger: %s", err)
	}

	resyncTrigger, err := trigger.NewTrigger(trigger.Parameters{
		Name:        "eni-node-manager-resync",
		MinInterval: time.Second,
		TriggerFunc: func(reasons []string) {
			instancesAPI.Resync()
			mngr.Resync()
		},
	})
	if err != nil {
		return nil, fmt.Errorf("unable to initialize resync trigger: %s", err)
	}

	mngr.resyncTrigger = resyncTrigger
	mngr.deficitResolver = deficitResolver

	return mngr, nil
}

// GetNames returns the list of all node names
func (n *NodeManager) GetNames() (allNodeNames []string) {
	n.mutex.RLock()
	defer n.mutex.RUnlock()

	allNodeNames = make([]string, 0, len(n.nodes))

	for name := range n.nodes {
		allNodeNames = append(allNodeNames, name)
	}

	return
}

// Update is called whenever a CiliumNode resource has been updated in the
// Kubernetes apiserver
func (n *NodeManager) Update(resource *v2.CiliumNode) bool {
	n.mutex.Lock()
	node, ok := n.nodes[resource.Name]
	if !ok {
		node = &Node{
			name:    resource.Name,
			manager: n,
		}
		n.nodes[node.name] = node

		log.WithField(fieldName, resource.Name).Info("Discovered new CiliumNode custom resource")
	}
	n.mutex.Unlock()

	return node.updatedResource(resource)
}

// Delete is called after a CiliumNode resource has been deleted via the
// Kubernetes apiserver
func (n *NodeManager) Delete(nodeName string) {
	n.mutex.Lock()
	delete(n.nodes, nodeName)
	n.mutex.Unlock()
}

// Get returns the node with the given name
func (n *NodeManager) Get(nodeName string) *Node {
	n.mutex.RLock()
	node := n.nodes[nodeName]
	n.mutex.RUnlock()
	return node
}

// GetNodesByNeededAddresses returns all nodes that require addresses to be
// allocated, sorted by the number of addresses needed in descending order
func (n *NodeManager) GetNodesByNeededAddresses() []*Node {
	n.mutex.RLock()
	defer n.mutex.RUnlock()

	list := make([]*Node, len(n.nodes))
	index := 0
	for _, node := range n.nodes {
		list[index] = node
		index++
	}

	sort.Slice(list, func(i, j int) bool {
		return list[i].getNeededAddresses() > list[j].getNeededAddresses()
	})

	return list
}

// Resync will attend all nodes and resolves IP deficits. The order of
// attendance is defined by the number of IPs needed to reach the configured
// watermarks. Any updates to the node resource are synchronized to the
// Kubernetes apiserver.
func (n *NodeManager) Resync() {
	var totalUsed, totalAvailable, totalNeeded, remainingInterfaces, nodesAtCapacity int

	for _, node := range n.GetNodesByNeededAddresses() {
		node.mutex.Lock()
		// Resync() is always called after resync of the instance data,
		// mark node as resynced
		node.resyncNeeded = false
		allocationNeeded := node.recalculateLocked()
		node.loggerLocked().WithFields(logrus.Fields{
			fieldName:   node.name,
			"available": node.stats.availableIPs,
			"used":      node.stats.usedIPs,
		}).Debug("Recalculated allocation requirements")
		totalUsed += node.stats.usedIPs
		totalAvailable += node.stats.availableIPs - node.stats.usedIPs
		totalNeeded += node.stats.neededIPs
		remainingInterfaces += node.stats.remainingInterfaces

		if remainingInterfaces == 0 && totalAvailable == 0 {
			nodesAtCapacity++
		}
		if allocationNeeded && node.stats.remainingInterfaces > 0 {
			n.deficitResolver.TriggerWithReason(node.name)
		}
		node.mutex.Unlock()

		node.SyncToAPIServer()
	}

	n.metricsAPI.SetAllocatedIPs("used", totalUsed)
	n.metricsAPI.SetAllocatedIPs("available", totalAvailable)
	n.metricsAPI.SetAllocatedIPs("needed", totalNeeded)
	n.metricsAPI.SetAvailableENIs(remainingInterfaces)
	n.metricsAPI.SetNodesAtCapacity(nodesAtCapacity)
}
<|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 (
	"bytes"
	"context"
	"fmt"
	"os"
	"testing"

	uuid "github.com/satori/go.uuid"
)

func TestGsecretsIntegration(t *testing.T) {
	t.Parallel()

	if testing.Short() {
		t.Skip("skipping integration test (short)")
	}

	ctx := context.Background()

	bucket := os.Getenv("GOOGLE_CLOUD_BUCKET")
	if bucket == "" {
		t.Fatal("missing GOOGLE_CLOUD_BUCKET")
	}

	key := os.Getenv("GOOGLE_CLOUD_KMS_KEY")
	if key == "" {
		t.Fatal("missing GOOGLE_CLOUD_KMS_KEY")
	}

	sa := os.Getenv("GOOGLE_CLOUD_SERVICE_ACCOUNT")
	if sa == "" {
		t.Fatal("missing GOOGLE_CLOUD_SERVICE_ACCOUNT")
	}
	sa = fmt.Sprintf("serviceAccount:%s", sa)

	object, object2 := testUUID(t), testUUID(t)
	if len(object) < 3 || len(object2) < 3 {
		t.Fatal("bad uuid created")
	}
	// ensure non-matching prefix
	for i := 0; i < 10 && object[:3] == object2[:3]; i++ {
		object2 = testUUID(t)
	}
	if object[:3] == object2[:3] {
		t.Fatal("unable to generate non-prefix matching uuids")
	}

	c, err := New(ctx)
	if err != nil {
		t.Fatal(err)
	}

	original := []byte("original text")
	var secret *Secret

	if secret, err = c.Create(ctx, &CreateRequest{
		Bucket:    bucket,
		Object:    object,
		Key:       key,
		Plaintext: original,
	}); err != nil {
		t.Fatal(err)
	}

	if _, err = c.Create(ctx, &CreateRequest{
		Bucket:    bucket,
		Object:    object2,
		Key:       key,
		Plaintext: original,
	}); err != nil {
		t.Fatal(err)
	}

	secrets, err := c.List(ctx, &ListRequest{
		Bucket: bucket,
	})
	if err != nil {
		t.Fatal(err)
	}
	if !testStringInclude(secrets.Secrets, object, secret.Generation) {
		t.Errorf("expected %#v to include %q", secrets, object)
	}
	if !testStringInclude(secrets.Secrets, object2, 0) {
		t.Errorf("expected %#v to include %q", secrets, object2)
	}

	secrets, err = c.List(ctx, &ListRequest{
		Bucket: bucket,
		Prefix: object[:3],
	})
	if err != nil {
		t.Fatal(err)
	}
	if !testStringInclude(secrets.Secrets, object, secret.Generation) {
		t.Errorf("expected %#v to include %q", secrets, object)
	}
	if testStringInclude(secrets.Secrets, object2, secret.Generation) {
		t.Errorf("expected %#v to not include %q", secrets, object)
	}

	updated := []byte("updated text")

	var updatedSecret *Secret
	if updatedSecret, err = c.Create(ctx, &CreateRequest{
		Bucket:    bucket,
		Object:    object,
		Key:       key,
		Plaintext: updated,
	}); err != nil {
		t.Fatal(err)
	}

	secrets, err = c.List(ctx, &ListRequest{
		Bucket:      bucket,
		Prefix:      object,
		Generations: true,
	})
	if err != nil {
		t.Fatal(err)
	}
	if !testStringInclude(secrets.Secrets, object, updatedSecret.Generation) {
		t.Errorf("expected %#v to include %q", secrets, object)
	}
	if !testStringInclude(secrets.Secrets, object, secret.Generation) {
		t.Errorf("expected %#v to include %q", secrets, object)
	}

	plaintext, err := c.Access(ctx, &AccessRequest{
		Bucket: bucket,
		Object: object,
	})
	if err != nil {
		t.Fatal(err)
	}
	if !bytes.Equal(plaintext, updated) {
		t.Errorf("expected %q to be %q", plaintext, updated)
	}

	plaintext, err = c.Access(ctx, &AccessRequest{
		Bucket:     bucket,
		Object:     object,
		Generation: secret.Generation,
	})
	if err != nil {
		t.Fatal(err)
	}
	if !bytes.Equal(plaintext, original) {
		t.Errorf("expected %q to be %q", plaintext, original)
	}

	if err := c.Grant(ctx, &GrantRequest{
		Bucket:  bucket,
		Object:  object,
		Members: []string{sa},
	}); err != nil {
		t.Fatal(err)
	}

	if err := c.Revoke(ctx, &RevokeRequest{
		Bucket:  bucket,
		Object:  object,
		Members: []string{sa},
	}); err != nil {
		t.Fatal(err)
	}

	if err := c.Delete(ctx, &DeleteRequest{
		Bucket: bucket,
		Object: object,
	}); err != nil {
		t.Fatal(err)
	}
}

func testStringInclude(l []*Secret, n string, g int64) bool {
	for _, v := range l {
		if n == v.Name && (g == 0 || g == v.Generation) {
			return true
		}
	}
	return false
}

func testUUID(tb testing.TB) string {
	tb.Helper()

	u, err := uuid.NewV4()
	if err != nil {
		tb.Fatal(err)
	}
	return u.String()
}

func TestKMSKeyTrimVersion(t *testing.T) {
	t.Parallel()

	cases := []struct {
		name string
		i    string
		o    string
	}{
		{
			"malformed",
			"foo",
			"foo",
		},
		{
			"no_version",
			"projects/p/locations/l/keyRings/kr/cryptoKeys/ck",
			"projects/p/locations/l/keyRings/kr/cryptoKeys/ck",
		},
		{
			"version",
			"projects/p/locations/l/keyRings/kr/cryptoKeys/ck/cryptoKeyVersions/1",
			"projects/p/locations/l/keyRings/kr/cryptoKeys/ck",
		},
	}

	for _, tc := range cases {
		tc := tc

		t.Run(tc.name, func(t *testing.T) {
			t.Parallel()

			if act, exp := kmsKeyTrimVersion(tc.i), tc.o; act != exp {
				t.Errorf("expected %q to be %q", act, exp)
			}
		})
	}
}
<commit_msg>Delete object2 too<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 (
	"bytes"
	"context"
	"fmt"
	"os"
	"testing"

	uuid "github.com/satori/go.uuid"
)

func TestGsecretsIntegration(t *testing.T) {
	t.Parallel()

	if testing.Short() {
		t.Skip("skipping integration test (short)")
	}

	ctx := context.Background()

	bucket := os.Getenv("GOOGLE_CLOUD_BUCKET")
	if bucket == "" {
		t.Fatal("missing GOOGLE_CLOUD_BUCKET")
	}

	key := os.Getenv("GOOGLE_CLOUD_KMS_KEY")
	if key == "" {
		t.Fatal("missing GOOGLE_CLOUD_KMS_KEY")
	}

	sa := os.Getenv("GOOGLE_CLOUD_SERVICE_ACCOUNT")
	if sa == "" {
		t.Fatal("missing GOOGLE_CLOUD_SERVICE_ACCOUNT")
	}
	sa = fmt.Sprintf("serviceAccount:%s", sa)

	object, object2 := testUUID(t), testUUID(t)
	if len(object) < 3 || len(object2) < 3 {
		t.Fatal("bad uuid created")
	}
	// ensure non-matching prefix
	for i := 0; i < 10 && object[:3] == object2[:3]; i++ {
		object2 = testUUID(t)
	}
	if object[:3] == object2[:3] {
		t.Fatal("unable to generate non-prefix matching uuids")
	}

	c, err := New(ctx)
	if err != nil {
		t.Fatal(err)
	}

	original := []byte("original text")
	var secret *Secret

	if secret, err = c.Create(ctx, &CreateRequest{
		Bucket:    bucket,
		Object:    object,
		Key:       key,
		Plaintext: original,
	}); err != nil {
		t.Fatal(err)
	}

	if _, err = c.Create(ctx, &CreateRequest{
		Bucket:    bucket,
		Object:    object2,
		Key:       key,
		Plaintext: original,
	}); err != nil {
		t.Fatal(err)
	}

	secrets, err := c.List(ctx, &ListRequest{
		Bucket: bucket,
	})
	if err != nil {
		t.Fatal(err)
	}
	if !testStringInclude(secrets.Secrets, object, secret.Generation) {
		t.Errorf("expected %#v to include %q", secrets, object)
	}
	if !testStringInclude(secrets.Secrets, object2, 0) {
		t.Errorf("expected %#v to include %q", secrets, object2)
	}

	secrets, err = c.List(ctx, &ListRequest{
		Bucket: bucket,
		Prefix: object[:3],
	})
	if err != nil {
		t.Fatal(err)
	}
	if !testStringInclude(secrets.Secrets, object, secret.Generation) {
		t.Errorf("expected %#v to include %q", secrets, object)
	}
	if testStringInclude(secrets.Secrets, object2, secret.Generation) {
		t.Errorf("expected %#v to not include %q", secrets, object)
	}

	updated := []byte("updated text")

	var updatedSecret *Secret
	if updatedSecret, err = c.Create(ctx, &CreateRequest{
		Bucket:    bucket,
		Object:    object,
		Key:       key,
		Plaintext: updated,
	}); err != nil {
		t.Fatal(err)
	}

	secrets, err = c.List(ctx, &ListRequest{
		Bucket:      bucket,
		Prefix:      object,
		Generations: true,
	})
	if err != nil {
		t.Fatal(err)
	}
	if !testStringInclude(secrets.Secrets, object, updatedSecret.Generation) {
		t.Errorf("expected %#v to include %q", secrets, object)
	}
	if !testStringInclude(secrets.Secrets, object, secret.Generation) {
		t.Errorf("expected %#v to include %q", secrets, object)
	}

	plaintext, err := c.Access(ctx, &AccessRequest{
		Bucket: bucket,
		Object: object,
	})
	if err != nil {
		t.Fatal(err)
	}
	if !bytes.Equal(plaintext, updated) {
		t.Errorf("expected %q to be %q", plaintext, updated)
	}

	plaintext, err = c.Access(ctx, &AccessRequest{
		Bucket:     bucket,
		Object:     object,
		Generation: secret.Generation,
	})
	if err != nil {
		t.Fatal(err)
	}
	if !bytes.Equal(plaintext, original) {
		t.Errorf("expected %q to be %q", plaintext, original)
	}

	if err := c.Grant(ctx, &GrantRequest{
		Bucket:  bucket,
		Object:  object,
		Members: []string{sa},
	}); err != nil {
		t.Fatal(err)
	}

	if err := c.Revoke(ctx, &RevokeRequest{
		Bucket:  bucket,
		Object:  object,
		Members: []string{sa},
	}); err != nil {
		t.Fatal(err)
	}

	if err := c.Delete(ctx, &DeleteRequest{
		Bucket: bucket,
		Object: object,
	}); err != nil {
		t.Fatal(err)
	}

	if err := c.Delete(ctx, &DeleteRequest{
		Bucket: bucket,
		Object: object2,
	}); err != nil {
		t.Fatal(err)
	}
}

func testStringInclude(l []*Secret, n string, g int64) bool {
	for _, v := range l {
		if n == v.Name && (g == 0 || g == v.Generation) {
			return true
		}
	}
	return false
}

func testUUID(tb testing.TB) string {
	tb.Helper()

	u, err := uuid.NewV4()
	if err != nil {
		tb.Fatal(err)
	}
	return u.String()
}

func TestKMSKeyTrimVersion(t *testing.T) {
	t.Parallel()

	cases := []struct {
		name string
		i    string
		o    string
	}{
		{
			"malformed",
			"foo",
			"foo",
		},
		{
			"no_version",
			"projects/p/locations/l/keyRings/kr/cryptoKeys/ck",
			"projects/p/locations/l/keyRings/kr/cryptoKeys/ck",
		},
		{
			"version",
			"projects/p/locations/l/keyRings/kr/cryptoKeys/ck/cryptoKeyVersions/1",
			"projects/p/locations/l/keyRings/kr/cryptoKeys/ck",
		},
	}

	for _, tc := range cases {
		tc := tc

		t.Run(tc.name, func(t *testing.T) {
			t.Parallel()

			if act, exp := kmsKeyTrimVersion(tc.i), tc.o; act != exp {
				t.Errorf("expected %q to be %q", act, exp)
			}
		})
	}
}
<|endoftext|>


<commit_before>// Copyright 2017-2018 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 netboot provides a one-stop shop for netboot parsing needs.
//
// netboot can take a URL from a DHCP lease and try to detect iPXE scripts and
// PXE scripts.
//
// TODO: detect multiboot and Linux kernels without configuration (URL points
// to a single kernel file).
//
// TODO: detect iSCSI root paths.
package netboot

import (
	"fmt"
	"log"
	"net"
	"net/url"
	"path"

	"github.com/u-root/u-root/pkg/boot"
	"github.com/u-root/u-root/pkg/boot/netboot/ipxe"
	"github.com/u-root/u-root/pkg/boot/netboot/pxe"
	"github.com/u-root/u-root/pkg/curl"
	"github.com/u-root/u-root/pkg/dhclient"
)

// BootImage figures out the image to boot from the given DHCP lease.
//
// Tries, in order:
//
// - to detect an iPXE script beginning with #!ipxe,
//
// - to detect a pxelinux.0, in which case we will ignore the pxelinux and try
//   to parse pxelinux.cfg/<files>.
//
// TODO: detect straight up multiboot and bzImage Linux kernel files rather
// than just configuration scripts.
func BootImage(s curl.Schemes, lease dhclient.Lease) (*boot.LinuxImage, error) {
	uri, err := lease.Boot()
	if err != nil {
		return nil, err
	}
	log.Printf("Boot URI: %s", uri)

	// IP only makes sense for v4 anyway, because the PXE probing of files
	// uses a MAC address and an IPv4 address to look at files.
	var ip net.IP
	if p4, ok := lease.(*dhclient.Packet4); ok {
		ip = p4.Lease().IP
	}
	return getBootImage(s, uri, lease.Link().Attrs().HardwareAddr, ip)
}

// getBootImage attempts to parse the file at uri as an ipxe config and returns
// the ipxe boot image. Otherwise falls back to pxe and uses the uri directory,
// ip, and mac address to search for pxe configs.
func getBootImage(schemes curl.Schemes, uri *url.URL, mac net.HardwareAddr, ip net.IP) (*boot.LinuxImage, error) {
	// Attempt to read the given boot path as an ipxe config file.
	ipc, err := ipxe.ParseConfigWithSchemes(uri, schemes)
	if err == nil {
		return ipc, nil
	}
	log.Printf("Falling back to pxe boot: %v", err)

	// Fallback to pxe boot.
	wd := &url.URL{
		Scheme: uri.Scheme,
		Host:   uri.Host,
		Path:   path.Dir(uri.Path),
	}
	pc, err := pxe.ParseConfigWithSchemes(wd, mac, ip, schemes)
	if err != nil {
		return nil, fmt.Errorf("failed to parse pxelinux config: %v", err)
	}

	label := pc.Entries[pc.DefaultEntry]
	return label, nil
}
<commit_msg>pkg/boot/netboot: Handle the error when a boot target is not found.<commit_after>// Copyright 2017-2018 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 netboot provides a one-stop shop for netboot parsing needs.
//
// netboot can take a URL from a DHCP lease and try to detect iPXE scripts and
// PXE scripts.
//
// TODO: detect multiboot and Linux kernels without configuration (URL points
// to a single kernel file).
//
// TODO: detect iSCSI root paths.
package netboot

import (
	"fmt"
	"log"
	"net"
	"net/url"
	"path"

	"github.com/u-root/u-root/pkg/boot"
	"github.com/u-root/u-root/pkg/boot/netboot/ipxe"
	"github.com/u-root/u-root/pkg/boot/netboot/pxe"
	"github.com/u-root/u-root/pkg/curl"
	"github.com/u-root/u-root/pkg/dhclient"
)

// BootImage figures out the image to boot from the given DHCP lease.
//
// Tries, in order:
//
// - to detect an iPXE script beginning with #!ipxe,
//
// - to detect a pxelinux.0, in which case we will ignore the pxelinux and try
//   to parse pxelinux.cfg/<files>.
//
// TODO: detect straight up multiboot and bzImage Linux kernel files rather
// than just configuration scripts.
func BootImage(s curl.Schemes, lease dhclient.Lease) (*boot.LinuxImage, error) {
	uri, err := lease.Boot()
	if err != nil {
		return nil, err
	}
	log.Printf("Boot URI: %s", uri)

	// IP only makes sense for v4 anyway, because the PXE probing of files
	// uses a MAC address and an IPv4 address to look at files.
	var ip net.IP
	if p4, ok := lease.(*dhclient.Packet4); ok {
		ip = p4.Lease().IP
	}
	return getBootImage(s, uri, lease.Link().Attrs().HardwareAddr, ip)
}

// getBootImage attempts to parse the file at uri as an ipxe config and returns
// the ipxe boot image. Otherwise falls back to pxe and uses the uri directory,
// ip, and mac address to search for pxe configs.
func getBootImage(schemes curl.Schemes, uri *url.URL, mac net.HardwareAddr, ip net.IP) (*boot.LinuxImage, error) {
	// Attempt to read the given boot path as an ipxe config file.
	ipc, err := ipxe.ParseConfigWithSchemes(uri, schemes)
	if err == nil {
		return ipc, nil
	}
	log.Printf("Falling back to pxe boot: %v", err)

	// Fallback to pxe boot.
	wd := &url.URL{
		Scheme: uri.Scheme,
		Host:   uri.Host,
		Path:   path.Dir(uri.Path),
	}
	pc, err := pxe.ParseConfigWithSchemes(wd, mac, ip, schemes)
	if err != nil {
		return nil, fmt.Errorf("failed to parse pxelinux config: %v", err)
	}

	label, ok := pc.Entries[pc.DefaultEntry]
	if !ok {
		return nil, fmt.Errorf("Could not find %q from entries %v", pc.DefaultEntry, pc.Entries)
	}
	return label, nil
}
<|endoftext|>


<commit_before>// Package ocb2 implements the version 2 of the OCB authenticated-encryption algorithm.
// OCB2 is specified in http://www.cs.ucdavis.edu/~rogaway/papers/draft-krovetz-ocb-00.txt.
//
// It should be noted that OCB's author, Phil Rogaway <rogaway@cs.ucdavis.edu>, holds
// several US patents on the algorithm.  This should be considered before using this code
// in your own projects.  See OCB's FAQ for more info:
// http://www.cs.ucdavis.edu/~rogaway/ocb/ocb-faq.htm#patent:phil
//
// The Mumble Project has a license to use OCB mode in its BSD licensed code on a royalty
// free basis.
package ocb2

import (
	"crypto/aes"
	"crypto/cipher"
)

const (
	// TagSize specifies the length in bytes of a full OCB2 tag.
	// As per the specification, applications may truncate their
	// tags to a given length, but advocates that typical applications
	// should use a tag length of at least 8 bytes (64 bits).
	TagSize = aes.BlockSize
	// NonceSize specifies the length in bytes of an OCB2 nonce.
	NonceSize = aes.BlockSize
)

// zeros fills block with zero bytes.
func zeros(block []byte) {
	for i := range block {
		block[i] = 0
	}
}

// xor outputs the bitwise exclusive-or of a and b to dst.
func xor(dst []byte, a []byte, b []byte) {
	for i := 0; i < aes.BlockSize; i++ {
		dst[i] = a[i] ^ b[i]
	}
}

// times2 performs the times2 operation, defined as:
//
// times2(S)
//     S << 1 if S[1] = 0, and (S << 1) xor const(bitlength(S)) if S[1] = 1.
//
// where const(n) is defined as
//
// const(n)
//     The lexicographically first n-bit string C among all
//     strings that have a minimal possible number of "1"
//     bits and which name a polynomial x^n + C[1] *
//     x^{n-1} + ... + C[n-1] * x^1 + C[n] * x^0 that is
//     irreducible over the field with two elements.  In
//     particular, const(128) = num2str(135, 128).  For
//     other values of n, refer to a standard table of
//     irreducible polynomials [G. Seroussi,
//     "Table of low-weight binary irreducible polynomials",
//     HP Labs Technical Report HPL-98-135, 1998.].
//
// and num2str(x, n) is defined as
//
// num2str(x, n)
//     The n-bit binary representation of the integer x.
//     More formally, the n-bit string S where x = S[1] *
//     2^{n-1} + S[2] * 2^{n-2} + ... + S[n] * 2^{0}.  Only
//     used when 0 <= x < 2^n.
//
// For our 128-bit block size implementation, this means that
// the xor with const(bitlength(S)) if S[1] = 1 is implemented
// by simply xor'ing the last byte with the number 135 when
// S[1] = 1.
func times2(block []byte) {
	carry := (block[0] >> 7) & 0x1
	for i := 0; i < aes.BlockSize-1; i++ {
		block[i] = (block[i] << 1) | ((block[i+1] >> 7) & 0x1)
	}
	block[aes.BlockSize-1] = (block[aes.BlockSize-1] << 1) ^ (carry * 135)
}

// times3 performs the times3 operation, defined as:
//
// times3(S)
//     times2(S) xor S
func times3(block []byte) {
	carry := (block[0] >> 7) & 0x1
	for i := 0; i < aes.BlockSize-1; i++ {
		block[i] ^= (block[i] << 1) | ((block[i+1] >> 7) & 0x1)
	}
	block[aes.BlockSize-1] ^= ((block[aes.BlockSize-1] << 1) ^ (carry * 135))
}

// Encrypt encrypts the plaintext src and outputs the corresponding ciphertext into dst.
// Besides outputting a ciphertext into dst, Encrypt also outputs an authentication tag
// of ocb2.TagSize bytes into tag, which should be used to verify the authenticity of the
// message on the receiving side.
//
// To ensure both authenticity and secrecy of messages, each invocation to this function must
// be given an unique nonce of ocb2.NonceSize bytes.  The nonce need not be secret (it can be
// a counter), but it needs to be unique.
func Encrypt(cipher cipher.Block, dst []byte, src []byte, nonce []byte, tag []byte) {
	var delta [aes.BlockSize]byte
	var checksum [aes.BlockSize]byte
	var tmp [aes.BlockSize]byte
	var pad [aes.BlockSize]byte
	off := 0

	cipher.Encrypt(delta[0:], nonce[0:])
	zeros(checksum[0:])

	remain := len(src)
	for remain > aes.BlockSize {
		times2(delta[0:])
		xor(tmp[0:], delta[0:], src[off:off+aes.BlockSize])
		cipher.Encrypt(tmp[0:], tmp[0:])
		xor(dst[off:off+aes.BlockSize], delta[0:], tmp[0:])
		xor(checksum[0:], checksum[0:], src[off:off+aes.BlockSize])
		remain -= aes.BlockSize
		off += aes.BlockSize
	}

	times2(delta[0:])
	zeros(tmp[0:])
	num := remain * 8
	tmp[aes.BlockSize-2] = uint8((uint32(num) >> 8) & 0xff)
	tmp[aes.BlockSize-1] = uint8(num & 0xff)
	xor(tmp[0:], tmp[0:], delta[0:])
	cipher.Encrypt(pad[0:], tmp[0:])
	copied := copy(tmp[0:], src[off:])
	if copied != remain {
		panic("ocb2: copy failed")
	}
	if copy(tmp[copied:], pad[copied:]) != (aes.BlockSize - remain) {
		panic("ocb2: copy failed")
	}
	xor(checksum[0:], checksum[0:], tmp[0:])
	xor(tmp[0:], pad[0:], tmp[0:])
	if copy(dst[off:], tmp[0:]) != remain {
		panic("ocb2: copy failed")
	}

	times3(delta[0:])
	xor(tmp[0:], delta[0:], checksum[0:])
	cipher.Encrypt(tag[0:], tmp[0:])
}

// Decrypt takes a ciphertext and a nonce as its input and outputs a decrypted plaintext
// and corresponding authentication tag.
//
// Before using the decrpyted plaintext, the application
// should verify that the computed authentication tag matches the tag that was produced when
// encrypting the message (taking into consideration that OCB tags are allowed to be truncated
// to a length less than ocb.TagSize).
func Decrypt(cipher cipher.Block, plain []byte, encrypted []byte, nonce []byte, tag []byte) {
	var checksum [aes.BlockSize]byte
	var delta [aes.BlockSize]byte
	var tmp [aes.BlockSize]byte
	var pad [aes.BlockSize]byte
	off := 0

	cipher.Encrypt(delta[0:], nonce[0:])
	zeros(checksum[0:])

	remain := len(encrypted)
	for remain > aes.BlockSize {
		times2(delta[0:])
		xor(tmp[0:], delta[0:], encrypted[off:off+aes.BlockSize])
		cipher.Decrypt(tmp[0:], tmp[0:])
		xor(plain[off:off+aes.BlockSize], delta[0:], tmp[0:])
		xor(checksum[0:], checksum[0:], plain[off:off+aes.BlockSize])
		off += aes.BlockSize
		remain -= aes.BlockSize
	}

	times2(delta[0:])
	zeros(tmp[0:])
	num := remain * 8
	tmp[aes.BlockSize-2] = uint8((uint32(num) >> 8) & 0xff)
	tmp[aes.BlockSize-1] = uint8(num & 0xff)
	xor(tmp[0:], tmp[0:], delta[0:])
	cipher.Encrypt(pad[0:], tmp[0:])
	zeros(tmp[0:])
	copied := copy(tmp[0:remain], encrypted[off:off+remain])
	if copied != remain {
		panic("ocb2: copy failed")
	}
	xor(tmp[0:], tmp[0:], pad[0:])
	xor(checksum[0:], checksum[0:], tmp[0:])
	copied = copy(plain[off:off+remain], tmp[0:remain])
	if copied != remain {
		panic("ocb2: copy failed")
	}

	times3(delta[0:])
	xor(tmp[0:], delta[0:], checksum[0:])
	cipher.Encrypt(tag[0:], tmp[0:])
}<commit_msg>pkg/cryptstate/ocb2: stricter cipher/tag/nonce checking.<commit_after>// Package ocb2 implements the version 2 of the OCB authenticated-encryption algorithm.
// OCB2 is specified in http://www.cs.ucdavis.edu/~rogaway/papers/draft-krovetz-ocb-00.txt.
//
// Note that this implementation is limited to block ciphers with a block size of 128 bits.
//
// It should also be noted that OCB's author, Phil Rogaway <rogaway@cs.ucdavis.edu>, holds
// several US patents on the algorithm.  This should be considered before using this code
// in your own projects.  See OCB's FAQ for more info:
// http://www.cs.ucdavis.edu/~rogaway/ocb/ocb-faq.htm#patent:phil
//
// The Mumble Project has a license to use OCB mode in its BSD licensed code on a royalty
// free basis.
package ocb2

import "crypto/cipher"

const (
	// BlockSize defines the block size that this particular implementation
	// of OCB2 is made to work on.
	BlockSize = 16
	// TagSize specifies the length in bytes of a full OCB2 tag.
	// As per the specification, applications may truncate their
	// tags to a given length, but advocates that typical applications
	// should use a tag length of at least 8 bytes (64 bits).
	TagSize = BlockSize
	// NonceSize specifies the length in bytes of an OCB2 nonce.
	NonceSize = BlockSize
)

// zeros fills block with zero bytes.
func zeros(block []byte) {
	for i := range block {
		block[i] = 0
	}
}

// xor outputs the bitwise exclusive-or of a and b to dst.
func xor(dst []byte, a []byte, b []byte) {
	for i := 0; i < BlockSize; i++ {
		dst[i] = a[i] ^ b[i]
	}
}

// times2 performs the times2 operation, defined as:
//
// times2(S)
//     S << 1 if S[1] = 0, and (S << 1) xor const(bitlength(S)) if S[1] = 1.
//
// where const(n) is defined as
//
// const(n)
//     The lexicographically first n-bit string C among all
//     strings that have a minimal possible number of "1"
//     bits and which name a polynomial x^n + C[1] *
//     x^{n-1} + ... + C[n-1] * x^1 + C[n] * x^0 that is
//     irreducible over the field with two elements.  In
//     particular, const(128) = num2str(135, 128).  For
//     other values of n, refer to a standard table of
//     irreducible polynomials [G. Seroussi,
//     "Table of low-weight binary irreducible polynomials",
//     HP Labs Technical Report HPL-98-135, 1998.].
//
// and num2str(x, n) is defined as
//
// num2str(x, n)
//     The n-bit binary representation of the integer x.
//     More formally, the n-bit string S where x = S[1] *
//     2^{n-1} + S[2] * 2^{n-2} + ... + S[n] * 2^{0}.  Only
//     used when 0 <= x < 2^n.
//
// For our 128-bit block size implementation, this means that
// the xor with const(bitlength(S)) if S[1] = 1 is implemented
// by simply xor'ing the last byte with the number 135 when
// S[1] = 1.
func times2(block []byte) {
	carry := (block[0] >> 7) & 0x1
	for i := 0; i < BlockSize-1; i++ {
		block[i] = (block[i] << 1) | ((block[i+1] >> 7) & 0x1)
	}
	block[BlockSize-1] = (block[BlockSize-1] << 1) ^ (carry * 135)
}

// times3 performs the times3 operation, defined as:
//
// times3(S)
//     times2(S) xor S
func times3(block []byte) {
	carry := (block[0] >> 7) & 0x1
	for i := 0; i < BlockSize-1; i++ {
		block[i] ^= (block[i] << 1) | ((block[i+1] >> 7) & 0x1)
	}
	block[BlockSize-1] ^= ((block[BlockSize-1] << 1) ^ (carry * 135))
}

// Encrypt encrypts the plaintext src and outputs the corresponding ciphertext into dst.
// Besides outputting a ciphertext into dst, Encrypt also outputs an authentication tag
// of ocb2.TagSize bytes into tag, which should be used to verify the authenticity of the
// message on the receiving side.
//
// To ensure both authenticity and secrecy of messages, each invocation to this function must
// be given an unique nonce of ocb2.NonceSize bytes.  The nonce need not be secret (it can be
// a counter), but it needs to be unique.
//
// The block cipher used in function must work on a block size equal to ocb2.BlockSize.
// The tag slice used in this function must have a length equal to ocb2.TagSize.
// The nonce slice used in this function must have a length equal to ocb2.NonceSize.
// If any of the above are violated, Encrypt will panic.
func Encrypt(cipher cipher.Block, dst []byte, src []byte, nonce []byte, tag []byte) {
	if cipher.BlockSize() != BlockSize {
		panic("ocb2: cipher blocksize is not equal to ocb2.BlockSize")
	}
	if len(nonce) != NonceSize {
		panic("ocb2: nonce length is not equal to ocb2.NonceSize")
	}
	if len(tag) != TagSize {
		panic("ocb2: tag length is not equal to ocb2.TagSize")
	}

	var delta [BlockSize]byte
	var checksum [BlockSize]byte
	var tmp [BlockSize]byte
	var pad [BlockSize]byte
	off := 0

	cipher.Encrypt(delta[0:], nonce[0:])
	zeros(checksum[0:])

	remain := len(src)
	for remain > BlockSize {
		times2(delta[0:])
		xor(tmp[0:], delta[0:], src[off:off+BlockSize])
		cipher.Encrypt(tmp[0:], tmp[0:])
		xor(dst[off:off+BlockSize], delta[0:], tmp[0:])
		xor(checksum[0:], checksum[0:], src[off:off+BlockSize])
		remain -= BlockSize
		off += BlockSize
	}

	times2(delta[0:])
	zeros(tmp[0:])
	num := remain * 8
	tmp[BlockSize-2] = uint8((uint32(num) >> 8) & 0xff)
	tmp[BlockSize-1] = uint8(num & 0xff)
	xor(tmp[0:], tmp[0:], delta[0:])
	cipher.Encrypt(pad[0:], tmp[0:])
	copied := copy(tmp[0:], src[off:])
	if copied != remain {
		panic("ocb2: copy failed")
	}
	if copy(tmp[copied:], pad[copied:]) != (BlockSize - remain) {
		panic("ocb2: copy failed")
	}
	xor(checksum[0:], checksum[0:], tmp[0:])
	xor(tmp[0:], pad[0:], tmp[0:])
	if copy(dst[off:], tmp[0:]) != remain {
		panic("ocb2: copy failed")
	}

	times3(delta[0:])
	xor(tmp[0:], delta[0:], checksum[0:])
	cipher.Encrypt(tag[0:], tmp[0:])
}

// Decrypt takes a ciphertext and a nonce as its input and outputs a decrypted plaintext
// and corresponding authentication tag.
//
// Before using the decrpyted plaintext, the application
// should verify that the computed authentication tag matches the tag that was produced when
// encrypting the message (taking into consideration that OCB tags are allowed to be truncated
// to a length less than ocb.TagSize).
//
// The block cipher used in function must work on a block size equal to ocb2.BlockSize.
// The tag slice used in this function must have a length equal to ocb2.TagSize.
// The nonce slice used in this function must have a length equal to ocb2.NonceSize.
// If any of the above are violated, Encrypt will panic.
func Decrypt(cipher cipher.Block, plain []byte, encrypted []byte, nonce []byte, tag []byte) {
	if cipher.BlockSize() != BlockSize {
		panic("ocb2: cipher blocksize is not equal to ocb2.BlockSize")
	}
	if len(nonce) != NonceSize {
		panic("ocb2: nonce length is not equal to ocb2.NonceSize")
	}
	if len(tag) != TagSize {
		panic("ocb2: tag length is not equal to ocb2.TagSize")
	}

	var checksum [BlockSize]byte
	var delta [BlockSize]byte
	var tmp [BlockSize]byte
	var pad [BlockSize]byte
	off := 0

	cipher.Encrypt(delta[0:], nonce[0:])
	zeros(checksum[0:])

	remain := len(encrypted)
	for remain > BlockSize {
		times2(delta[0:])
		xor(tmp[0:], delta[0:], encrypted[off:off+BlockSize])
		cipher.Decrypt(tmp[0:], tmp[0:])
		xor(plain[off:off+BlockSize], delta[0:], tmp[0:])
		xor(checksum[0:], checksum[0:], plain[off:off+BlockSize])
		off += BlockSize
		remain -= BlockSize
	}

	times2(delta[0:])
	zeros(tmp[0:])
	num := remain * 8
	tmp[BlockSize-2] = uint8((uint32(num) >> 8) & 0xff)
	tmp[BlockSize-1] = uint8(num & 0xff)
	xor(tmp[0:], tmp[0:], delta[0:])
	cipher.Encrypt(pad[0:], tmp[0:])
	zeros(tmp[0:])
	copied := copy(tmp[0:remain], encrypted[off:off+remain])
	if copied != remain {
		panic("ocb2: copy failed")
	}
	xor(tmp[0:], tmp[0:], pad[0:])
	xor(checksum[0:], checksum[0:], tmp[0:])
	copied = copy(plain[off:off+remain], tmp[0:remain])
	if copied != remain {
		panic("ocb2: copy failed")
	}

	times3(delta[0:])
	xor(tmp[0:], delta[0:], checksum[0:])
	cipher.Encrypt(tag[0:], tmp[0:])
}<|endoftext|>


<commit_before>package handler

import (
	"net/http"
	"net/http/httputil"
	"net/url"

	"github.com/gorilla/mux"
	coreHttp "github.com/skygeario/skygear-server/pkg/core/http"
)

// NewGearHandler takes an incoming request and sends it to coresponding
// gear server
func NewGearHandler(restPathIdentifier string) http.HandlerFunc {
	proxy := newGearReverseProxy()
	return rewriteHandler(proxy, restPathIdentifier)
}

func newGearReverseProxy() *httputil.ReverseProxy {
	director := func(req *http.Request) {
		path := req.URL.Path
		query := req.URL.RawQuery
		fragment := req.URL.Fragment
		var err error
		u, err := url.Parse(req.Header.Get(coreHttp.HeaderGearEndpoint))
		if err != nil {
			panic(err)
		}
		req.URL = u
		req.URL.Path = path
		req.URL.RawQuery = query
		req.URL.Fragment = fragment
	}
	return &httputil.ReverseProxy{Director: director}
}

func rewriteHandler(p *httputil.ReverseProxy, restPathIdentifier string) func(http.ResponseWriter, *http.Request) {
	return func(w http.ResponseWriter, r *http.Request) {
		r.URL.Path = "/" + mux.Vars(r)[restPathIdentifier]
		p.ServeHTTP(w, r)
	}
}
<commit_msg>Remove CORS headers from upstream response<commit_after>package handler

import (
	"net/http"
	"net/http/httputil"
	"net/url"

	"github.com/gorilla/mux"
	coreHttp "github.com/skygeario/skygear-server/pkg/core/http"
)

// NewGearHandler takes an incoming request and sends it to coresponding
// gear server
func NewGearHandler(restPathIdentifier string) http.HandlerFunc {
	proxy := newGearReverseProxy()
	return rewriteHandler(proxy, restPathIdentifier)
}

func newGearReverseProxy() *httputil.ReverseProxy {
	director := func(req *http.Request) {
		path := req.URL.Path
		query := req.URL.RawQuery
		fragment := req.URL.Fragment
		var err error
		u, err := url.Parse(req.Header.Get(coreHttp.HeaderGearEndpoint))
		if err != nil {
			panic(err)
		}
		req.URL = u
		req.URL.Path = path
		req.URL.RawQuery = query
		req.URL.Fragment = fragment
	}
	modifyResponse := func(r *http.Response) error {
		// Remove CORS headers because they are managed by this gateway.
		// Auth gear in standalone mode mounts CORS middleware.
		// If we do not remove CORS headers, then the headers will duplicate.
		r.Header.Del("Access-Control-Allow-Origin")
		r.Header.Del("Access-Control-Allow-Credentials")
		r.Header.Del("Access-Control-Allow-Methods")
		r.Header.Del("Access-Control-Allow-Headers")
		return nil
	}

	return &httputil.ReverseProxy{Director: director, ModifyResponse: modifyResponse}
}

func rewriteHandler(p *httputil.ReverseProxy, restPathIdentifier string) func(http.ResponseWriter, *http.Request) {
	return func(w http.ResponseWriter, r *http.Request) {
		r.URL.Path = "/" + mux.Vars(r)[restPathIdentifier]
		p.ServeHTTP(w, r)
	}
}
<|endoftext|>


<commit_before>// Copyright (C) 2014 Constantin Schomburg <me@cschomburg.com>
//
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file.

package natural

import (
	"reflect"
	"testing"

	"github.com/xconstruct/stark/proto"
)

type simple struct {
	text            string
	ok              bool
	expected        proto.Message
	expectedPayload map[string]interface{}
}

func TestParseSimple(t *testing.T) {
	tests := []simple{
		simple{".ping", true, proto.Message{
			Action: "ping",
		}, nil},

		simple{".ping device=me", true, proto.Message{
			Action:      "ping",
			Destination: "me",
		}, nil},

		simple{".ping some text", true, proto.Message{
			Action: "ping",
			Text:   "some text",
		}, nil},

		simple{"!ping some text", true, proto.Message{
			Action: "ping",
			Text:   "some text",
		}, nil},

		simple{".ping with some device=me host=another things", true, proto.Message{
			Action:      "ping",
			Destination: "me",
			Text:        "with some things",
		}, map[string]interface{}{
			"host": "another",
		}},

		simple{`.ping with "some device=me" host="another things" this`, true, proto.Message{
			Action: "ping",
			Text:   `with some device=me this`,
		}, map[string]interface{}{
			"host": "another things",
		}},

		simple{"ping no", false, proto.Message{}, nil},
		simple{"", false, proto.Message{}, nil},
	}

	for _, test := range tests {
		msg, ok := ParseSimple(test.text)
		if ok != test.ok {
			t.Errorf("'%s' should parse? exp: %v, got: %v", test.text, test.ok, ok)
		} else if ok {
			var payload map[string]interface{}
			msg.DecodePayload(&payload)
			msg.Payload = nil
			if !reflect.DeepEqual(msg, test.expected) {
				t.Errorf("decoded message differs\nexp '%v'\ngot '%v'", test.expected, msg)
			}
			if !reflect.DeepEqual(payload, test.expectedPayload) {
				t.Errorf("decoded payload differs\nexp '%v'\ngot '%v'", test.expectedPayload, payload)
			}
		}
	}
}

func TestFormatMessage(t *testing.T) {
	msg := proto.Message{
		Text: "Hello, the time is 2015-03-14T18:48:10+02:00.",
	}
	FormatMessage(&msg)
	exp := "Hello, the time is Sat, 14 Mar 15 at 18:48."
	if msg.Text != exp {
		t.Error("Unexpected format: ", msg.Text)
	}
}
<commit_msg>Natural: Fix test after payload changes.<commit_after>// Copyright (C) 2014 Constantin Schomburg <me@cschomburg.com>
//
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file.

package natural

import (
	"reflect"
	"testing"

	"github.com/xconstruct/stark/proto"
)

type simple struct {
	text            string
	ok              bool
	expected        proto.Message
	expectedPayload map[string]interface{}
}

func TestParseSimple(t *testing.T) {
	tests := []simple{
		simple{".ping", true, proto.Message{
			Action: "ping",
		}, nil},

		simple{".ping device=me", true, proto.Message{
			Action:      "ping",
			Destination: "me",
		}, nil},

		simple{".ping some text", true, proto.Message{
			Action: "ping",
			Text:   "some text",
		}, nil},

		simple{"!ping some text", true, proto.Message{
			Action: "ping",
			Text:   "some text",
		}, nil},

		simple{".ping with some device=me host=another things", true, proto.Message{
			Action:      "ping",
			Destination: "me",
			Text:        "with some things",
		}, map[string]interface{}{
			"host": "another",
		}},

		simple{`.ping with "some device=me" host="another things" this`, true, proto.Message{
			Action: "ping",
			Text:   `with some device=me this`,
		}, map[string]interface{}{
			"host": "another things",
		}},

		simple{"ping no", false, proto.Message{}, nil},
		simple{"", false, proto.Message{}, nil},
	}

	for _, test := range tests {
		msg, ok := ParseSimple(test.text)
		if ok != test.ok {
			t.Errorf("'%s' should parse? exp: %v, got: %v", test.text, test.ok, ok)
		} else if ok {
			var payload map[string]interface{}
			msg.DecodePayload(&payload)
			msg.Payload.Raw = nil
			if !reflect.DeepEqual(msg, test.expected) {
				t.Errorf("decoded message differs\nexp '%v'\ngot '%v'", test.expected, msg)
			}
			if !reflect.DeepEqual(payload, test.expectedPayload) {
				t.Errorf("decoded payload differs\nexp '%v'\ngot '%v'", test.expectedPayload, payload)
			}
		}
	}
}

func TestFormatMessage(t *testing.T) {
	msg := proto.Message{
		Text: "Hello, the time is 2015-03-14T18:48:10+02:00.",
	}
	FormatMessage(&msg)
	exp := "Hello, the time is Sat, 14 Mar 15 at 18:48."
	if msg.Text != exp {
		t.Error("Unexpected format: ", msg.Text)
	}
}
<|endoftext|>


<commit_before>package restapi

import (
	"crypto/tls"
	"log"
	"net"
	"net/http"
	"os"
	"strconv"
	"sync"
	"time"

	"github.com/go-openapi/swag"
	flags "github.com/jessevdk/go-flags"
	graceful "github.com/tylerb/graceful"

	"github.com/radanalyticsio/oshinko-cli/rest/restapi/operations"
)

const (
	schemeHTTP  = "http"
	schemeHTTPS = "https"
	schemeUnix  = "unix"
)

var defaultSchemes []string

func init() {
	defaultSchemes = []string{
		schemeHTTP,
		schemeHTTPS,
	}
}

// NewServer creates a new api oshinko rest server but does not configure it
func NewServer(api *operations.OshinkoRestAPI) *Server {
	s := new(Server)
	s.api = api
	return s
}

// ConfigureAPI configures the API and handlers. Needs to be called before Serve
func (s *Server) ConfigureAPI() {
	if s.api != nil {
		s.handler = configureAPI(s.api)
	}
}

// ConfigureFlags configures the additional flags defined by the handlers. Needs to be called before the parser.Parse
func (s *Server) ConfigureFlags() {
	if s.api != nil {
		configureFlags(s.api)
	}
}

// Server for the oshinko rest API
type Server struct {
	EnabledListeners []string `long:"scheme" description:"the listeners to enable, this can be repeated and defaults to the schemes in the swagger spec"`

	SocketPath    flags.Filename `long:"socket-path" description:"the unix socket to listen on" default:"/var/run/oshinko-rest.sock"`
	domainSocketL net.Listener

	Host        string `long:"host" description:"the IP to listen on" default:"localhost" env:"HOST"`
	Port        int    `long:"port" description:"the port to listen on for insecure connections, defaults to a random value" env:"PORT"`
	httpServerL net.Listener

	TLSHost           string         `long:"tls-host" description:"the IP to listen on for tls, when not specified it's the same as --host" env:"TLS_HOST"`
	TLSPort           int            `long:"tls-port" description:"the port to listen on for secure connections, defaults to a random value" env:"TLS_PORT"`
	TLSCertificate    flags.Filename `long:"tls-certificate" description:"the certificate to use for secure connections" env:"TLS_CERTIFICATE"`
	TLSCertificateKey flags.Filename `long:"tls-key" description:"the private key to use for secure conections" env:"TLS_PRIVATE_KEY"`
	httpsServerL      net.Listener

	api          *operations.OshinkoRestAPI
	handler      http.Handler
	hasListeners bool
}

// Logf logs message either via defined user logger or via system one if no user logger is defined.
func (s *Server) Logf(f string, args ...interface{}) {
	if s.api != nil && s.api.Logger != nil {
		s.api.Logger(f, args...)
	} else {
		log.Printf(f, args...)
	}
}

// Fatalf logs message either via defined user logger or via system one if no user logger is defined.
// Exits with non-zero status after printing
func (s *Server) Fatalf(f string, args ...interface{}) {
	if s.api != nil && s.api.Logger != nil {
		s.api.Logger(f, args...)
		os.Exit(1)
	} else {
		log.Fatalf(f, args...)
	}
}

// SetAPI configures the server with the specified API. Needs to be called before Serve
func (s *Server) SetAPI(api *operations.OshinkoRestAPI) {
	if api == nil {
		s.api = nil
		s.handler = nil
		return
	}

	s.api = api
	s.api.Logger = log.Printf
	s.handler = configureAPI(api)
}

func (s *Server) hasScheme(scheme string) bool {
	schemes := s.EnabledListeners
	if len(schemes) == 0 {
		schemes = defaultSchemes
	}

	for _, v := range schemes {
		if v == scheme {
			return true
		}
	}
	return false
}

// Serve the api
func (s *Server) Serve() (err error) {
	if !s.hasListeners {
		if err := s.Listen(); err != nil {
			return err
		}
	}

	var wg sync.WaitGroup

	if s.hasScheme(schemeUnix) {
		domainSocket := &graceful.Server{Server: new(http.Server)}
		domainSocket.Handler = s.handler

		wg.Add(1)
		s.Logf("Serving oshinko rest at unix://%s", s.SocketPath)
		go func(l net.Listener) {
			defer wg.Done()
			if err := domainSocket.Serve(l); err != nil {
				s.Fatalf("%v", err)
			}
			s.Logf("Stopped serving oshinko rest at unix://%s", s.SocketPath)
		}(s.domainSocketL)
	}

	if s.hasScheme(schemeHTTP) {
		httpServer := &graceful.Server{Server: new(http.Server)}
		httpServer.SetKeepAlivesEnabled(true)
		httpServer.TCPKeepAlive = 3 * time.Minute
		httpServer.Handler = s.handler

		wg.Add(1)
		s.Logf("Serving oshinko rest at http://%s", s.httpServerL.Addr())
		go func(l net.Listener) {
			defer wg.Done()
			if err := httpServer.Serve(l); err != nil {
				s.Fatalf("%v", err)
			}
			s.Logf("Stopped serving oshinko rest at http://%s", l.Addr())
		}(s.httpServerL)
	}

	if s.hasScheme(schemeHTTPS) {
		httpsServer := &graceful.Server{Server: new(http.Server)}
		httpsServer.SetKeepAlivesEnabled(true)
		httpsServer.TCPKeepAlive = 3 * time.Minute
		httpsServer.Handler = s.handler

		httpsServer.TLSConfig = new(tls.Config)
		httpsServer.TLSConfig.NextProtos = []string{"http/1.1"}
		// https://www.owasp.org/index.php/Transport_Layer_Protection_Cheat_Sheet#Rule_-_Only_Support_Strong_Protocols
		httpsServer.TLSConfig.MinVersion = tls.VersionTLS12
		httpsServer.TLSConfig.Certificates = make([]tls.Certificate, 1)
		httpsServer.TLSConfig.Certificates[0], err = tls.LoadX509KeyPair(string(s.TLSCertificate), string(s.TLSCertificateKey))

		configureTLS(httpsServer.TLSConfig)

		if err != nil {
			return err
		}

		wg.Add(1)
		s.Logf("Serving oshinko rest at https://%s", s.httpsServerL.Addr())
		go func(l net.Listener) {
			defer wg.Done()
			if err := httpsServer.Serve(l); err != nil {
				s.Fatalf("%v", err)
			}
			s.Logf("Stopped serving oshinko rest at https://%s", l.Addr())
		}(tls.NewListener(s.httpsServerL, httpsServer.TLSConfig))
	}

	wg.Wait()
	return nil
}

// Listen creates the listeners for the server
func (s *Server) Listen() error {
	if s.hasListeners { // already done this
		return nil
	}

	if s.hasScheme(schemeHTTPS) { // exit early on missing params
		if s.TLSCertificate == "" {
			if s.TLSCertificateKey == "" {
				s.Fatalf("the required flags `--tls-certificate` and `--tls-key` were not specified")
			}
			s.Fatalf("the required flag `--tls-certificate` was not specified")
		}
		if s.TLSCertificateKey == "" {
			s.Fatalf("the required flag `--tls-key` was not specified")
		}

		// Use http host if https host wasn't defined
		if s.TLSHost == "" {
			s.TLSHost = s.Host
		}
	}

	if s.hasScheme(schemeUnix) {
		domSockListener, err := net.Listen("unix", string(s.SocketPath))
		if err != nil {
			return err
		}
		s.domainSocketL = domSockListener
	}

	if s.hasScheme(schemeHTTP) {
		listener, err := net.Listen("tcp", net.JoinHostPort(s.Host, strconv.Itoa(s.Port)))
		if err != nil {
			return err
		}

		h, p, err := swag.SplitHostPort(listener.Addr().String())
		if err != nil {
			return err
		}
		s.Host = h
		s.Port = p
		s.httpServerL = listener
	}

	if s.hasScheme(schemeHTTPS) {
		tlsListener, err := net.Listen("tcp", net.JoinHostPort(s.TLSHost, strconv.Itoa(s.TLSPort)))
		if err != nil {
			return err
		}

		sh, sp, err := swag.SplitHostPort(tlsListener.Addr().String())
		if err != nil {
			return err
		}
		s.TLSHost = sh
		s.TLSPort = sp
		s.httpsServerL = tlsListener
	}

	s.hasListeners = true
	return nil
}

// Shutdown server and clean up resources
func (s *Server) Shutdown() error {
	s.api.ServerShutdown()
	return nil
}

// GetHandler returns a handler useful for testing
func (s *Server) GetHandler() http.Handler {
	return s.handler
}
<commit_msg>Restore --info flag<commit_after>package restapi

import (
	"crypto/tls"
	"log"
	"net"
	"net/http"
	"os"
	"strconv"
	"sync"
	"time"

	"github.com/go-openapi/swag"
	flags "github.com/jessevdk/go-flags"
	graceful "github.com/tylerb/graceful"

	oshinkoFlags "github.com/radanalyticsio/oshinko-cli/rest/helpers/flags"
	"github.com/radanalyticsio/oshinko-cli/rest/helpers/info"
	"github.com/radanalyticsio/oshinko-cli/rest/restapi/operations"
	"github.com/radanalyticsio/oshinko-cli/rest/version"
)

const (
	schemeHTTP  = "http"
	schemeHTTPS = "https"
	schemeUnix  = "unix"
)

var defaultSchemes []string

func init() {
	defaultSchemes = []string{
		schemeHTTP,
		schemeHTTPS,
	}
}

// NewServer creates a new api oshinko rest server but does not configure it
func NewServer(api *operations.OshinkoRestAPI) *Server {
	s := new(Server)
	s.api = api
	return s
}

// ConfigureAPI configures the API and handlers. Needs to be called before Serve
func (s *Server) ConfigureAPI() {
	if s.api != nil {
		s.handler = configureAPI(s.api)
	}
}

// ConfigureFlags configures the additional flags defined by the handlers. Needs to be called before the parser.Parse
func (s *Server) ConfigureFlags() {
	if s.api != nil {
		configureFlags(s.api)
	}
}

// Server for the oshinko rest API
type Server struct {
	EnabledListeners []string `long:"scheme" description:"the listeners to enable, this can be repeated and defaults to the schemes in the swagger spec"`

	SocketPath    flags.Filename `long:"socket-path" description:"the unix socket to listen on" default:"/var/run/oshinko-rest.sock"`
	domainSocketL net.Listener

	Host        string `long:"host" description:"the IP to listen on" default:"localhost" env:"HOST"`
	Port        int    `long:"port" description:"the port to listen on for insecure connections, defaults to a random value" env:"PORT"`
	httpServerL net.Listener

	TLSHost           string         `long:"tls-host" description:"the IP to listen on for tls, when not specified it's the same as --host" env:"TLS_HOST"`
	TLSPort           int            `long:"tls-port" description:"the port to listen on for secure connections, defaults to a random value" env:"TLS_PORT"`
	TLSCertificate    flags.Filename `long:"tls-certificate" description:"the certificate to use for secure connections" env:"TLS_CERTIFICATE"`
	TLSCertificateKey flags.Filename `long:"tls-key" description:"the private key to use for secure conections" env:"TLS_PRIVATE_KEY"`
	httpsServerL      net.Listener

	api          *operations.OshinkoRestAPI
	handler      http.Handler
	hasListeners bool
}

// Logf logs message either via defined user logger or via system one if no user logger is defined.
func (s *Server) Logf(f string, args ...interface{}) {
	if s.api != nil && s.api.Logger != nil {
		s.api.Logger(f, args...)
	} else {
		log.Printf(f, args...)
	}
}

// Fatalf logs message either via defined user logger or via system one if no user logger is defined.
// Exits with non-zero status after printing
func (s *Server) Fatalf(f string, args ...interface{}) {
	if s.api != nil && s.api.Logger != nil {
		s.api.Logger(f, args...)
		os.Exit(1)
	} else {
		log.Fatalf(f, args...)
	}
}

// SetAPI configures the server with the specified API. Needs to be called before Serve
func (s *Server) SetAPI(api *operations.OshinkoRestAPI) {
	if api == nil {
		s.api = nil
		s.handler = nil
		return
	}

	s.api = api
	s.api.Logger = log.Printf
	s.handler = configureAPI(api)
}

func (s *Server) hasScheme(scheme string) bool {
	schemes := s.EnabledListeners
	if len(schemes) == 0 {
		schemes = defaultSchemes
	}

	for _, v := range schemes {
		if v == scheme {
			return true
		}
	}
	return false
}

// Serve the api
func (s *Server) Serve() (err error) {

	if oshinkoFlags.InfoEnabled() {
		img := info.GetSparkImage()
		log.Println(version.GetAppName() + " " + version.GetVersion())
		log.Println("Default cluster image: " + img)
		os.Exit(0)
	}

	if !s.hasListeners {
		if err := s.Listen(); err != nil {
			return err
		}
	}

	var wg sync.WaitGroup

	if s.hasScheme(schemeUnix) {
		domainSocket := &graceful.Server{Server: new(http.Server)}
		domainSocket.Handler = s.handler

		wg.Add(1)
		s.Logf("Serving oshinko rest at unix://%s", s.SocketPath)
		go func(l net.Listener) {
			defer wg.Done()
			if err := domainSocket.Serve(l); err != nil {
				s.Fatalf("%v", err)
			}
			s.Logf("Stopped serving oshinko rest at unix://%s", s.SocketPath)
		}(s.domainSocketL)
	}

	if s.hasScheme(schemeHTTP) {
		httpServer := &graceful.Server{Server: new(http.Server)}
		httpServer.SetKeepAlivesEnabled(true)
		httpServer.TCPKeepAlive = 3 * time.Minute
		httpServer.Handler = s.handler

		wg.Add(1)
		s.Logf("Serving oshinko rest at http://%s", s.httpServerL.Addr())
		go func(l net.Listener) {
			defer wg.Done()
			if err := httpServer.Serve(l); err != nil {
				s.Fatalf("%v", err)
			}
			s.Logf("Stopped serving oshinko rest at http://%s", l.Addr())
		}(s.httpServerL)
	}

	if s.hasScheme(schemeHTTPS) {
		httpsServer := &graceful.Server{Server: new(http.Server)}
		httpsServer.SetKeepAlivesEnabled(true)
		httpsServer.TCPKeepAlive = 3 * time.Minute
		httpsServer.Handler = s.handler

		httpsServer.TLSConfig = new(tls.Config)
		httpsServer.TLSConfig.NextProtos = []string{"http/1.1"}
		// https://www.owasp.org/index.php/Transport_Layer_Protection_Cheat_Sheet#Rule_-_Only_Support_Strong_Protocols
		httpsServer.TLSConfig.MinVersion = tls.VersionTLS12
		httpsServer.TLSConfig.Certificates = make([]tls.Certificate, 1)
		httpsServer.TLSConfig.Certificates[0], err = tls.LoadX509KeyPair(string(s.TLSCertificate), string(s.TLSCertificateKey))

		configureTLS(httpsServer.TLSConfig)

		if err != nil {
			return err
		}

		wg.Add(1)
		s.Logf("Serving oshinko rest at https://%s", s.httpsServerL.Addr())
		go func(l net.Listener) {
			defer wg.Done()
			if err := httpsServer.Serve(l); err != nil {
				s.Fatalf("%v", err)
			}
			s.Logf("Stopped serving oshinko rest at https://%s", l.Addr())
		}(tls.NewListener(s.httpsServerL, httpsServer.TLSConfig))
	}

	wg.Wait()
	return nil
}

// Listen creates the listeners for the server
func (s *Server) Listen() error {
	if s.hasListeners { // already done this
		return nil
	}

	if s.hasScheme(schemeHTTPS) { // exit early on missing params
		if s.TLSCertificate == "" {
			if s.TLSCertificateKey == "" {
				s.Fatalf("the required flags `--tls-certificate` and `--tls-key` were not specified")
			}
			s.Fatalf("the required flag `--tls-certificate` was not specified")
		}
		if s.TLSCertificateKey == "" {
			s.Fatalf("the required flag `--tls-key` was not specified")
		}

		// Use http host if https host wasn't defined
		if s.TLSHost == "" {
			s.TLSHost = s.Host
		}
	}

	if s.hasScheme(schemeUnix) {
		domSockListener, err := net.Listen("unix", string(s.SocketPath))
		if err != nil {
			return err
		}
		s.domainSocketL = domSockListener
	}

	if s.hasScheme(schemeHTTP) {
		listener, err := net.Listen("tcp", net.JoinHostPort(s.Host, strconv.Itoa(s.Port)))
		if err != nil {
			return err
		}

		h, p, err := swag.SplitHostPort(listener.Addr().String())
		if err != nil {
			return err
		}
		s.Host = h
		s.Port = p
		s.httpServerL = listener
	}

	if s.hasScheme(schemeHTTPS) {
		tlsListener, err := net.Listen("tcp", net.JoinHostPort(s.TLSHost, strconv.Itoa(s.TLSPort)))
		if err != nil {
			return err
		}

		sh, sp, err := swag.SplitHostPort(tlsListener.Addr().String())
		if err != nil {
			return err
		}
		s.TLSHost = sh
		s.TLSPort = sp
		s.httpsServerL = tlsListener
	}

	s.hasListeners = true
	return nil
}

// Shutdown server and clean up resources
func (s *Server) Shutdown() error {
	s.api.ServerShutdown()
	return nil
}

// GetHandler returns a handler useful for testing
func (s *Server) GetHandler() http.Handler {
	return s.handler
}
<|endoftext|>