<commit_before>/*
Copyright 2014 Google Inc. All rights reserved.

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

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

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

package client

import (
	"crypto/tls"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"time"

	"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
	"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
	"github.com/GoogleCloudPlatform/kubernetes/pkg/version"
	"github.com/GoogleCloudPlatform/kubernetes/pkg/watch"
	"github.com/golang/glog"
)

// Interface holds the methods for clients of Kubenetes,
// an interface to allow mock testing.
// TODO: these should return/take pointers.
type Interface interface {
	PodInterface
	ReplicationControllerInterface
	ServiceInterface
	VersionInterface
}

// PodInterface has methods to work with Pod resources
type PodInterface interface {
	ListPods(selector labels.Selector) (api.PodList, error)
	GetPod(name string) (api.Pod, error)
	DeletePod(name string) error
	CreatePod(api.Pod) (api.Pod, error)
	UpdatePod(api.Pod) (api.Pod, error)
}

// ReplicationControllerInterface has methods to work with ReplicationController resources
type ReplicationControllerInterface interface {
	ListReplicationControllers(selector labels.Selector) (api.ReplicationControllerList, error)
	GetReplicationController(name string) (api.ReplicationController, error)
	CreateReplicationController(api.ReplicationController) (api.ReplicationController, error)
	UpdateReplicationController(api.ReplicationController) (api.ReplicationController, error)
	DeleteReplicationController(string) error
	WatchReplicationControllers(label, field labels.Selector, resourceVersion uint64) (watch.Interface, error)
}

// ServiceInterface has methods to work with Service resources
type ServiceInterface interface {
	GetService(name string) (api.Service, error)
	CreateService(api.Service) (api.Service, error)
	UpdateService(api.Service) (api.Service, error)
	DeleteService(string) error
}

// VersionInterface has a method to retrieve the server version
type VersionInterface interface {
	ServerVersion() (*version.Info, error)
}

// Client is the actual implementation of a Kubernetes client.
type Client struct {
	*RESTClient
}

// StatusErr might get returned from an api call if your request is still being processed
// and hence the expected return data is not available yet.
type StatusErr struct {
	Status api.Status
}

func (s *StatusErr) Error() string {
	return fmt.Sprintf("Status: %v (%#v)", s.Status.Status, s.Status)
}

// AuthInfo is used to store authorization information
type AuthInfo struct {
	User     string
	Password string
}

// RESTClient holds common code used to work with API resources that follow the
// Kubernetes API pattern
// Host is the http://... base for the URL
type RESTClient struct {
	host       string
	auth       *AuthInfo
	httpClient *http.Client
	Sync       bool
	PollPeriod time.Duration
	Timeout    time.Duration
	Prefix     string
}

// NewRESTClient creates a new RESTClient. This client performs generic REST functions
// such as Get, Put, Post, and Delete on specified paths.
func NewRESTClient(host string, auth *AuthInfo, prefix string) *RESTClient {
	return &RESTClient{
		auth: auth,
		host: host,
		httpClient: &http.Client{
			Transport: &http.Transport{
				TLSClientConfig: &tls.Config{
					InsecureSkipVerify: true,
				},
			},
		},
		Sync:       false,
		PollPeriod: time.Second * 2,
		Timeout:    time.Second * 20,
		Prefix:     prefix,
	}

}

// New creates a Kubernetes client. This client works with pods, replication controllers
// and services. It allows operations such as list, get, update and delete on these objects.
func New(host string, auth *AuthInfo) *Client {
	return &Client{NewRESTClient(host, auth, "/api/v1beta1/")}
}

// Execute a request, adds authentication (if auth != nil), and HTTPS cert ignoring.
func (c *RESTClient) doRequest(request *http.Request) ([]byte, error) {
	if c.auth != nil {
		request.SetBasicAuth(c.auth.User, c.auth.Password)
	}
	response, err := c.httpClient.Do(request)
	if err != nil {
		return nil, err
	}
	defer response.Body.Close()
	body, err := ioutil.ReadAll(response.Body)
	if err != nil {
		return body, err
	}

	// Did the server give us a status response?
	isStatusResponse := false
	var status api.Status
	if err := api.DecodeInto(body, &status); err == nil && status.Status != "" {
		isStatusResponse = true
	}

	switch {
	case response.StatusCode == http.StatusConflict:
		// Return error given by server, if there was one.
		if isStatusResponse {
			return nil, &StatusErr{status}
		}
		fallthrough
	case response.StatusCode < http.StatusOK || response.StatusCode > http.StatusPartialContent:
		return nil, fmt.Errorf("request [%#v] failed (%d) %s: %s", request, response.StatusCode, response.Status, string(body))
	}

	// If the server gave us a status back, look at what it was.
	if isStatusResponse && status.Status != api.StatusSuccess {
		// "Working" requests need to be handled specially.
		// "Failed" requests are clearly just an error and it makes sense to return them as such.
		return nil, &StatusErr{status}
	}
	return body, err
}

// Underlying base implementation of performing a request.
// method is the HTTP method (e.g. "GET")
// path is the path on the host to hit
// requestBody is the body of the request. Can be nil.
// target the interface to marshal the JSON response into.  Can be nil.
func (c *RESTClient) rawRequest(method, path string, requestBody io.Reader, target interface{}) ([]byte, error) {
	request, err := http.NewRequest(method, c.makeURL(path), requestBody)
	if err != nil {
		return nil, err
	}
	body, err := c.doRequest(request)
	if err != nil {
		return body, err
	}
	if target != nil {
		err = api.DecodeInto(body, target)
	}
	if err != nil {
		glog.Infof("Failed to parse: %s\n", string(body))
		// FIXME: no need to return err here?
	}
	return body, err
}

func (c *RESTClient) makeURL(path string) string {
	return c.host + c.Prefix + path
}

// ListPods takes a selector, and returns the list of pods that match that selector
func (c *Client) ListPods(selector labels.Selector) (result api.PodList, err error) {
	err = c.Get().Path("pods").SelectorParam("labels", selector).Do().Into(&result)
	return
}

// GetPod takes the name of the pod, and returns the corresponding Pod object, and an error if it occurs
func (c *Client) GetPod(name string) (result api.Pod, err error) {
	err = c.Get().Path("pods").Path(name).Do().Into(&result)
	return
}

// DeletePod takes the name of the pod, and returns an error if one occurs
func (c *Client) DeletePod(name string) error {
	return c.Delete().Path("pods").Path(name).Do().Error()
}

// CreatePod takes the representation of a pod.  Returns the server's representation of the pod, and an error, if it occurs
func (c *Client) CreatePod(pod api.Pod) (result api.Pod, err error) {
	err = c.Post().Path("pods").Body(pod).Do().Into(&result)
	return
}

// UpdatePod takes the representation of a pod to update.  Returns the server's representation of the pod, and an error, if it occurs
func (c *Client) UpdatePod(pod api.Pod) (result api.Pod, err error) {
	if pod.ResourceVersion == 0 {
		err = fmt.Errorf("invalid update object, missing resource version: %v", pod)
		return
	}
	err = c.Put().Path("pods").Path(pod.ID).Body(pod).Do().Into(&result)
	return
}

// ListReplicationControllers takes a selector, and returns the list of replication controllers that match that selector
func (c *Client) ListReplicationControllers(selector labels.Selector) (result api.ReplicationControllerList, err error) {
	err = c.Get().Path("replicationControllers").SelectorParam("labels", selector).Do().Into(&result)
	return
}

// GetReplicationController returns information about a particular replication controller
func (c *Client) GetReplicationController(name string) (result api.ReplicationController, err error) {
	err = c.Get().Path("replicationControllers").Path(name).Do().Into(&result)
	return
}

// CreateReplicationController creates a new replication controller
func (c *Client) CreateReplicationController(controller api.ReplicationController) (result api.ReplicationController, err error) {
	err = c.Post().Path("replicationControllers").Body(controller).Do().Into(&result)
	return
}

// UpdateReplicationController updates an existing replication controller
func (c *Client) UpdateReplicationController(controller api.ReplicationController) (result api.ReplicationController, err error) {
	if controller.ResourceVersion == 0 {
		err = fmt.Errorf("invalid update object, missing resource version: %v", controller)
		return
	}
	err = c.Put().Path("replicationControllers").Path(controller.ID).Body(controller).Do().Into(&result)
	return
}

// DeleteReplicationController deletes an existing replication controller.
func (c *Client) DeleteReplicationController(name string) error {
	return c.Delete().Path("replicationControllers").Path(name).Do().Error()
}

// WatchReplicationControllers returns a watch.Interface that watches the requested controllers.
func (c *Client) WatchReplicationControllers(label, field labels.Selector, resourceVersion uint64) (watch.Interface, error) {
	return c.Get().
		Path("watch").
		Path("replicationControllers").
		UintParam("resourceVersion", resourceVersion).
		SelectorParam("labels", label).
		SelectorParam("fields", field).
		Watch()
}

// GetService returns information about a particular service.
func (c *Client) GetService(name string) (result api.Service, err error) {
	err = c.Get().Path("services").Path(name).Do().Into(&result)
	return
}

// CreateService creates a new service.
func (c *Client) CreateService(svc api.Service) (result api.Service, err error) {
	err = c.Post().Path("services").Body(svc).Do().Into(&result)
	return
}

// UpdateService updates an existing service.
func (c *Client) UpdateService(svc api.Service) (result api.Service, err error) {
	if svc.ResourceVersion == 0 {
		err = fmt.Errorf("invalid update object, missing resource version: %v", svc)
		return
	}
	err = c.Put().Path("services").Path(svc.ID).Body(svc).Do().Into(&result)
	return
}

// DeleteService deletes an existing service.
func (c *Client) DeleteService(name string) error {
	return c.Delete().Path("services").Path(name).Do().Error()
}

// ServerVersion retrieves and parses the server's version.
func (c *Client) ServerVersion() (*version.Info, error) {
	body, err := c.Get().AbsPath("/version").Do().Raw()
	if err != nil {
		return nil, err
	}
	var info version.Info
	err = json.Unmarshal(body, &info)
	if err != nil {
		return nil, fmt.Errorf("Got '%s': %v", string(body), err)
	}
	return &info, nil
}
<commit_msg>Adding ListMinions() API to pkg/client.<commit_after>/*
Copyright 2014 Google Inc. All rights reserved.

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

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

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

package client

import (
	"crypto/tls"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"time"

	"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
	"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
	"github.com/GoogleCloudPlatform/kubernetes/pkg/version"
	"github.com/GoogleCloudPlatform/kubernetes/pkg/watch"
	"github.com/golang/glog"
)

// Interface holds the methods for clients of Kubenetes,
// an interface to allow mock testing.
// TODO: these should return/take pointers.
type Interface interface {
	PodInterface
	ReplicationControllerInterface
	ServiceInterface
	VersionInterface
	MinionInterface
}

// PodInterface has methods to work with Pod resources
type PodInterface interface {
	ListPods(selector labels.Selector) (api.PodList, error)
	GetPod(name string) (api.Pod, error)
	DeletePod(name string) error
	CreatePod(api.Pod) (api.Pod, error)
	UpdatePod(api.Pod) (api.Pod, error)
}

// ReplicationControllerInterface has methods to work with ReplicationController resources
type ReplicationControllerInterface interface {
	ListReplicationControllers(selector labels.Selector) (api.ReplicationControllerList, error)
	GetReplicationController(name string) (api.ReplicationController, error)
	CreateReplicationController(api.ReplicationController) (api.ReplicationController, error)
	UpdateReplicationController(api.ReplicationController) (api.ReplicationController, error)
	DeleteReplicationController(string) error
	WatchReplicationControllers(label, field labels.Selector, resourceVersion uint64) (watch.Interface, error)
}

// ServiceInterface has methods to work with Service resources
type ServiceInterface interface {
	GetService(name string) (api.Service, error)
	CreateService(api.Service) (api.Service, error)
	UpdateService(api.Service) (api.Service, error)
	DeleteService(string) error
}

// VersionInterface has a method to retrieve the server version
type VersionInterface interface {
	ServerVersion() (*version.Info, error)
}

type MinionInterface interface {
	ListMinions() (api.MinionList, error)
}

// Client is the actual implementation of a Kubernetes client.
type Client struct {
	*RESTClient
}

// StatusErr might get returned from an api call if your request is still being processed
// and hence the expected return data is not available yet.
type StatusErr struct {
	Status api.Status
}

func (s *StatusErr) Error() string {
	return fmt.Sprintf("Status: %v (%#v)", s.Status.Status, s.Status)
}

// AuthInfo is used to store authorization information
type AuthInfo struct {
	User     string
	Password string
}

// RESTClient holds common code used to work with API resources that follow the
// Kubernetes API pattern
// Host is the http://... base for the URL
type RESTClient struct {
	host       string
	auth       *AuthInfo
	httpClient *http.Client
	Sync       bool
	PollPeriod time.Duration
	Timeout    time.Duration
	Prefix     string
}

// NewRESTClient creates a new RESTClient. This client performs generic REST functions
// such as Get, Put, Post, and Delete on specified paths.
func NewRESTClient(host string, auth *AuthInfo, prefix string) *RESTClient {
	return &RESTClient{
		auth: auth,
		host: host,
		httpClient: &http.Client{
			Transport: &http.Transport{
				TLSClientConfig: &tls.Config{
					InsecureSkipVerify: true,
				},
			},
		},
		Sync:       false,
		PollPeriod: time.Second * 2,
		Timeout:    time.Second * 20,
		Prefix:     prefix,
	}

}

// New creates a Kubernetes client. This client works with pods, replication controllers
// and services. It allows operations such as list, get, update and delete on these objects.
func New(host string, auth *AuthInfo) *Client {
	return &Client{NewRESTClient(host, auth, "/api/v1beta1/")}
}

// Execute a request, adds authentication (if auth != nil), and HTTPS cert ignoring.
func (c *RESTClient) doRequest(request *http.Request) ([]byte, error) {
	if c.auth != nil {
		request.SetBasicAuth(c.auth.User, c.auth.Password)
	}
	response, err := c.httpClient.Do(request)
	if err != nil {
		return nil, err
	}
	defer response.Body.Close()
	body, err := ioutil.ReadAll(response.Body)
	if err != nil {
		return body, err
	}

	// Did the server give us a status response?
	isStatusResponse := false
	var status api.Status
	if err := api.DecodeInto(body, &status); err == nil && status.Status != "" {
		isStatusResponse = true
	}

	switch {
	case response.StatusCode == http.StatusConflict:
		// Return error given by server, if there was one.
		if isStatusResponse {
			return nil, &StatusErr{status}
		}
		fallthrough
	case response.StatusCode < http.StatusOK || response.StatusCode > http.StatusPartialContent:
		return nil, fmt.Errorf("request [%#v] failed (%d) %s: %s", request, response.StatusCode, response.Status, string(body))
	}

	// If the server gave us a status back, look at what it was.
	if isStatusResponse && status.Status != api.StatusSuccess {
		// "Working" requests need to be handled specially.
		// "Failed" requests are clearly just an error and it makes sense to return them as such.
		return nil, &StatusErr{status}
	}
	return body, err
}

// Underlying base implementation of performing a request.
// method is the HTTP method (e.g. "GET")
// path is the path on the host to hit
// requestBody is the body of the request. Can be nil.
// target the interface to marshal the JSON response into.  Can be nil.
func (c *RESTClient) rawRequest(method, path string, requestBody io.Reader, target interface{}) ([]byte, error) {
	request, err := http.NewRequest(method, c.makeURL(path), requestBody)
	if err != nil {
		return nil, err
	}
	body, err := c.doRequest(request)
	if err != nil {
		return body, err
	}
	if target != nil {
		err = api.DecodeInto(body, target)
	}
	if err != nil {
		glog.Infof("Failed to parse: %s\n", string(body))
		// FIXME: no need to return err here?
	}
	return body, err
}

func (c *RESTClient) makeURL(path string) string {
	return c.host + c.Prefix + path
}

// ListPods takes a selector, and returns the list of pods that match that selector
func (c *Client) ListPods(selector labels.Selector) (result api.PodList, err error) {
	err = c.Get().Path("pods").SelectorParam("labels", selector).Do().Into(&result)
	return
}

// GetPod takes the name of the pod, and returns the corresponding Pod object, and an error if it occurs
func (c *Client) GetPod(name string) (result api.Pod, err error) {
	err = c.Get().Path("pods").Path(name).Do().Into(&result)
	return
}

// DeletePod takes the name of the pod, and returns an error if one occurs
func (c *Client) DeletePod(name string) error {
	return c.Delete().Path("pods").Path(name).Do().Error()
}

// CreatePod takes the representation of a pod.  Returns the server's representation of the pod, and an error, if it occurs
func (c *Client) CreatePod(pod api.Pod) (result api.Pod, err error) {
	err = c.Post().Path("pods").Body(pod).Do().Into(&result)
	return
}

// UpdatePod takes the representation of a pod to update.  Returns the server's representation of the pod, and an error, if it occurs
func (c *Client) UpdatePod(pod api.Pod) (result api.Pod, err error) {
	if pod.ResourceVersion == 0 {
		err = fmt.Errorf("invalid update object, missing resource version: %v", pod)
		return
	}
	err = c.Put().Path("pods").Path(pod.ID).Body(pod).Do().Into(&result)
	return
}

// ListReplicationControllers takes a selector, and returns the list of replication controllers that match that selector
func (c *Client) ListReplicationControllers(selector labels.Selector) (result api.ReplicationControllerList, err error) {
	err = c.Get().Path("replicationControllers").SelectorParam("labels", selector).Do().Into(&result)
	return
}

// GetReplicationController returns information about a particular replication controller
func (c *Client) GetReplicationController(name string) (result api.ReplicationController, err error) {
	err = c.Get().Path("replicationControllers").Path(name).Do().Into(&result)
	return
}

// CreateReplicationController creates a new replication controller
func (c *Client) CreateReplicationController(controller api.ReplicationController) (result api.ReplicationController, err error) {
	err = c.Post().Path("replicationControllers").Body(controller).Do().Into(&result)
	return
}

// UpdateReplicationController updates an existing replication controller
func (c *Client) UpdateReplicationController(controller api.ReplicationController) (result api.ReplicationController, err error) {
	if controller.ResourceVersion == 0 {
		err = fmt.Errorf("invalid update object, missing resource version: %v", controller)
		return
	}
	err = c.Put().Path("replicationControllers").Path(controller.ID).Body(controller).Do().Into(&result)
	return
}

// DeleteReplicationController deletes an existing replication controller.
func (c *Client) DeleteReplicationController(name string) error {
	return c.Delete().Path("replicationControllers").Path(name).Do().Error()
}

// WatchReplicationControllers returns a watch.Interface that watches the requested controllers.
func (c *Client) WatchReplicationControllers(label, field labels.Selector, resourceVersion uint64) (watch.Interface, error) {
	return c.Get().
		Path("watch").
		Path("replicationControllers").
		UintParam("resourceVersion", resourceVersion).
		SelectorParam("labels", label).
		SelectorParam("fields", field).
		Watch()
}

// GetService returns information about a particular service.
func (c *Client) GetService(name string) (result api.Service, err error) {
	err = c.Get().Path("services").Path(name).Do().Into(&result)
	return
}

// CreateService creates a new service.
func (c *Client) CreateService(svc api.Service) (result api.Service, err error) {
	err = c.Post().Path("services").Body(svc).Do().Into(&result)
	return
}

// UpdateService updates an existing service.
func (c *Client) UpdateService(svc api.Service) (result api.Service, err error) {
	if svc.ResourceVersion == 0 {
		err = fmt.Errorf("invalid update object, missing resource version: %v", svc)
		return
	}
	err = c.Put().Path("services").Path(svc.ID).Body(svc).Do().Into(&result)
	return
}

// DeleteService deletes an existing service.
func (c *Client) DeleteService(name string) error {
	return c.Delete().Path("services").Path(name).Do().Error()
}

// ServerVersion retrieves and parses the server's version.
func (c *Client) ServerVersion() (*version.Info, error) {
	body, err := c.Get().AbsPath("/version").Do().Raw()
	if err != nil {
		return nil, err
	}
	var info version.Info
	err = json.Unmarshal(body, &info)
	if err != nil {
		return nil, fmt.Errorf("Got '%s': %v", string(body), err)
	}
	return &info, nil
}

func (c *Client) ListMinions() (minionList api.MinionList, err error) {
	err = c.Get().Path("minions").Do().Into(&minionList)
	return
}
<|endoftext|>


<commit_before>/*
Copyright 2018, 2019 the Velero contributors.

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 client

import (
	"encoding/json"
	"os"
	"path/filepath"
	"strings"

	"github.com/pkg/errors"
)

const (
	ConfigKeyNamespace = "namespace"
	ConfigKeyFeatures  = "features"
)

// VeleroConfig is a map of strings to interface{} for deserializing Velero client config options.
// The alias is a way to attach type-asserting convenience methods.
type VeleroConfig map[string]interface{}

// LoadConfig loads the Velero client configuration file and returns it as a VeleroConfig. If the
// file does not exist, an empty map is returned.
func LoadConfig() (VeleroConfig, error) {
	fileName := configFileName()

	_, err := os.Stat(fileName)
	if os.IsNotExist(err) {
		// If the file isn't there, just return an empty map
		return VeleroConfig{}, nil
	}
	if err != nil {
		// For any other Stat() error, return it
		return nil, errors.WithStack(err)
	}

	configFile, err := os.Open(fileName)
	if err != nil {
		return nil, errors.WithStack(err)
	}
	defer configFile.Close()

	var config VeleroConfig
	if err := json.NewDecoder(configFile).Decode(&config); err != nil {
		return nil, errors.WithStack(err)
	}

	return config, nil
}

// SaveConfig saves the passed in config map to the Velero client configuration file.
func SaveConfig(config VeleroConfig) error {
	fileName := configFileName()

	// Try to make the directory in case it doesn't exist
	dir := filepath.Dir(fileName)
	if err := os.MkdirAll(dir, 0755); err != nil {
		return errors.WithStack(err)
	}

	configFile, err := os.OpenFile(fileName, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0755)
	if err != nil {
		return errors.WithStack(err)
	}
	defer configFile.Close()

	return json.NewEncoder(configFile).Encode(&config)
}

func (c VeleroConfig) Namespace() string {
	val, ok := c[ConfigKeyNamespace]
	if !ok {
		return ""
	}

	ns, ok := val.(string)
	if !ok {
		return ""
	}

	return ns
}

func (c VeleroConfig) Features() []string {
	val, ok := c[ConfigKeyFeatures]
	if !ok {
		return []string{}
	}

	features, ok := val.(string)
	if !ok {
		return []string{}
	}

	return strings.Split(features, ",")
}

func configFileName() string {
	return filepath.Join(os.Getenv("HOME"), ".config", "velero", "config.json")
}
<commit_msg>Restrict file permissions for config file/dir<commit_after>/*
Copyright 2018, 2019 the Velero contributors.

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 client

import (
	"encoding/json"
	"os"
	"path/filepath"
	"strings"

	"github.com/pkg/errors"
)

const (
	ConfigKeyNamespace = "namespace"
	ConfigKeyFeatures  = "features"
)

// VeleroConfig is a map of strings to interface{} for deserializing Velero client config options.
// The alias is a way to attach type-asserting convenience methods.
type VeleroConfig map[string]interface{}

// LoadConfig loads the Velero client configuration file and returns it as a VeleroConfig. If the
// file does not exist, an empty map is returned.
func LoadConfig() (VeleroConfig, error) {
	fileName := configFileName()

	_, err := os.Stat(fileName)
	if os.IsNotExist(err) {
		// If the file isn't there, just return an empty map
		return VeleroConfig{}, nil
	}
	if err != nil {
		// For any other Stat() error, return it
		return nil, errors.WithStack(err)
	}

	configFile, err := os.Open(fileName)
	if err != nil {
		return nil, errors.WithStack(err)
	}
	defer configFile.Close()

	var config VeleroConfig
	if err := json.NewDecoder(configFile).Decode(&config); err != nil {
		return nil, errors.WithStack(err)
	}

	return config, nil
}

// SaveConfig saves the passed in config map to the Velero client configuration file.
func SaveConfig(config VeleroConfig) error {
	fileName := configFileName()

	// Try to make the directory in case it doesn't exist
	dir := filepath.Dir(fileName)
	if err := os.MkdirAll(dir, 0700); err != nil {
		return errors.WithStack(err)
	}

	configFile, err := os.OpenFile(fileName, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0600)
	if err != nil {
		return errors.WithStack(err)
	}
	defer configFile.Close()

	return json.NewEncoder(configFile).Encode(&config)
}

func (c VeleroConfig) Namespace() string {
	val, ok := c[ConfigKeyNamespace]
	if !ok {
		return ""
	}

	ns, ok := val.(string)
	if !ok {
		return ""
	}

	return ns
}

func (c VeleroConfig) Features() []string {
	val, ok := c[ConfigKeyFeatures]
	if !ok {
		return []string{}
	}

	features, ok := val.(string)
	if !ok {
		return []string{}
	}

	return strings.Split(features, ",")
}

func configFileName() string {
	return filepath.Join(os.Getenv("HOME"), ".config", "velero", "config.json")
}
<|endoftext|>


<commit_before>/*
 * This file is part of the KubeVirt project
 *
 * 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.
 *
 * Copyright 2018 Red Hat, Inc.
 *
 */

package hooks

import (
	"context"
	"encoding/json"
	"encoding/xml"
	"fmt"
	"io/ioutil"
	"net"
	"sort"
	"strings"
	"sync"
	"time"

	"google.golang.org/grpc"

	v1 "kubevirt.io/kubevirt/pkg/api/v1"
	hooksInfo "kubevirt.io/kubevirt/pkg/hooks/info"
	hooksV1alpha1 "kubevirt.io/kubevirt/pkg/hooks/v1alpha1"
	hooksV1alpha2 "kubevirt.io/kubevirt/pkg/hooks/v1alpha2"
	"kubevirt.io/kubevirt/pkg/log"
	virtwrapApi "kubevirt.io/kubevirt/pkg/virt-launcher/virtwrap/api"
)

type callBackClient struct {
	SocketPath          string
	Version             string
	subsribedHookPoints []*hooksInfo.HookPoint
}

var manager *Manager
var once sync.Once

type Manager struct {
	callbacksPerHookPoint map[string][]*callBackClient
}

func GetManager() *Manager {
	once.Do(func() {
		manager = &Manager{callbacksPerHookPoint: make(map[string][]*callBackClient)}
	})
	return manager
}

func (m *Manager) Collect(numberOfRequestedHookSidecars uint, timeout time.Duration) error {
	callbacksPerHookPoint, err := collectSideCarSockets(numberOfRequestedHookSidecars, timeout)
	if err != nil {
		return err
	}
	log.Log.Info("Collected all requested hook sidecar sockets")

	sortCallbacksPerHookPoint(callbacksPerHookPoint)
	log.Log.Infof("Sorted all collected sidecar sockets per hook point based on their priority and name: %v", callbacksPerHookPoint)

	m.callbacksPerHookPoint = callbacksPerHookPoint

	return nil
}

// TODO: Handle sockets in parallel, when a socket appears, run a goroutine trying to read Info from it
func collectSideCarSockets(numberOfRequestedHookSidecars uint, timeout time.Duration) (map[string][]*callBackClient, error) {
	callbacksPerHookPoint := make(map[string][]*callBackClient)
	processedSockets := make(map[string]bool)

	timeoutCh := time.After(timeout)

	for uint(len(processedSockets)) < numberOfRequestedHookSidecars {
		sockets, err := ioutil.ReadDir(HookSocketsSharedDirectory)
		if err != nil {
			return nil, err
		}

		for _, socket := range sockets {
			select {
			case <-timeoutCh:
				return nil, fmt.Errorf("Failed to collect all expected sidecar hook sockets within given timeout")
			default:
				if _, processed := processedSockets[socket.Name()]; processed {
					continue
				}

				callBackClient, notReady, err := processSideCarSocket(HookSocketsSharedDirectory + "/" + socket.Name())
				if notReady {
					log.Log.Info("Sidecar server might not be ready yet, retrying in the next iteration")
					continue
				} else if err != nil {
					log.Log.Reason(err).Infof("Failed to process sidecar socket: %s", socket.Name())
					return nil, err
				}

				for _, subsribedHookPoint := range callBackClient.subsribedHookPoints {
					callbacksPerHookPoint[subsribedHookPoint.GetName()] = append(callbacksPerHookPoint[subsribedHookPoint.GetName()], callBackClient)
				}

				processedSockets[socket.Name()] = true
			}
		}

		time.Sleep(time.Second)
	}

	return callbacksPerHookPoint, nil
}

func processSideCarSocket(socketPath string) (*callBackClient, bool, error) {
	conn, err := dialSocket(socketPath)
	if err != nil {
		log.Log.Reason(err).Infof("Failed to Dial hook socket: %s", socketPath)
		return nil, true, nil
	}
	defer conn.Close()

	infoClient := hooksInfo.NewInfoClient(conn)
	ctx, cancel := context.WithTimeout(context.Background(), time.Second)
	defer cancel()
	info, err := infoClient.Info(ctx, &hooksInfo.InfoParams{})
	if err != nil {
		return nil, false, err
	}

	versionsSet := make(map[string]bool)
	for _, version := range info.GetVersions() {
		versionsSet[version] = true
	}

	if _, found := versionsSet[hooksV1alpha2.Version]; found {
		return &callBackClient{
			SocketPath:          socketPath,
			Version:             hooksV1alpha2.Version,
			subsribedHookPoints: info.GetHookPoints(),
		}, false, nil
	} else if _, found := versionsSet[hooksV1alpha1.Version]; found {
		return &callBackClient{
			SocketPath:          socketPath,
			Version:             hooksV1alpha1.Version,
			subsribedHookPoints: info.GetHookPoints(),
		}, false, nil
	} else {
		return nil, false,
			fmt.Errorf("Hook sidecar does not expose a supported version. Exposed versions: %v, supported versions: %v",
				info.GetVersions(), []string{hooksV1alpha1.Version, hooksV1alpha2.Version})
	}
}

func sortCallbacksPerHookPoint(callbacksPerHookPoint map[string][]*callBackClient) {
	for _, callbacks := range callbacksPerHookPoint {
		for _, callback := range callbacks {
			sort.Slice(callbacks, func(i, j int) bool {
				if callback.subsribedHookPoints[i].Priority == callback.subsribedHookPoints[j].Priority {
					return strings.Compare(callback.subsribedHookPoints[i].Name, callback.subsribedHookPoints[j].Name) < 0
				} else {
					return callback.subsribedHookPoints[i].Priority > callback.subsribedHookPoints[j].Priority
				}
			})
		}
	}
}

func (m *Manager) OnDefineDomain(domainSpec *virtwrapApi.DomainSpec, vmi *v1.VirtualMachineInstance) (string, error) {
	domainSpecXML, err := xml.Marshal(domainSpec)
	if err != nil {
		return "", fmt.Errorf("Failed to marshal domain spec: %v", domainSpec)
	}
	if callbacks, found := m.callbacksPerHookPoint[hooksInfo.OnDefineDomainHookPointName]; found {
		for _, callback := range callbacks {
			if callback.Version == hooksV1alpha1.Version {

				vmiJSON, err := json.Marshal(vmi)
				if err != nil {
					return "", fmt.Errorf("Failed to marshal VMI spec: %v", vmi)
				}

				conn, err := dialSocket(callback.SocketPath)
				if err != nil {
					log.Log.Reason(err).Infof("Failed to Dial hook socket: %s", callback.SocketPath)
					return "", err
				}
				defer conn.Close()

				client := hooksV1alpha1.NewCallbacksClient(conn)

				ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
				defer cancel()
				result, err := client.OnDefineDomain(ctx, &hooksV1alpha1.OnDefineDomainParams{
					DomainXML: domainSpecXML,
					Vmi:       vmiJSON,
				})
				if err != nil {
					return "", err
				}
				domainSpecXML = result.GetDomainXML()
			} else {
				panic("Should never happen, version compatibility check is done during Info call")
			}
		}
	}
	return string(domainSpecXML), nil
}

func (m *Manager) PreCloudInitIso(vmi *v1.VirtualMachineInstance, cloudInitData *v1.CloudInitNoCloudSource) (*v1.CloudInitNoCloudSource, error) {
	if callbacks, found := m.callbacksPerHookPoint[hooksInfo.PreCloudInitIsoHookPointName]; found {
		for _, callback := range callbacks {
			if callback.Version == hooksV1alpha2.Version {
				var resultSource *v1.CloudInitNoCloudSource
				vmiJSON, err := json.Marshal(vmi)
				if err != nil {
					return cloudInitData, fmt.Errorf("Failed to marshal VMI spec: %v", vmi)
				}

				cloudInitDataJSON, err := json.Marshal(cloudInitData)
				if err != nil {
					return cloudInitData, fmt.Errorf("Failed to marshal CloudInitNoCloudSource: %v", cloudInitData)
				}

				conn, err := dialSocket(callback.SocketPath)
				if err != nil {
					log.Log.Reason(err).Infof("Failed to Dial hook socket: %s", callback.SocketPath)
					return cloudInitData, err
				}
				defer conn.Close()

				client := hooksV1alpha2.NewCallbacksClient(conn)
				ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
				defer cancel()
				result, err := client.PreCloudInitIso(ctx, &hooksV1alpha2.PreCloudInitIsoParams{
					CloudInitData: cloudInitDataJSON,
					Vmi:           vmiJSON,
				})
				if err != nil {
					return cloudInitData, err
				}

				err = json.Unmarshal(result.GetCloudInitData(), &resultSource)
				if err != nil {
					return cloudInitData, err
				}
				return resultSource, nil
			} else {
				panic("Should never happen, version compatibility check is done during Info call")
			}
		}
	}
	return cloudInitData, nil
}

func dialSocket(socketPath string) (*grpc.ClientConn, error) {
	return grpc.Dial(
		socketPath,
		grpc.WithInsecure(),
		grpc.WithDialer(func(addr string, timeout time.Duration) (net.Conn, error) {
			return net.DialTimeout("unix", addr, timeout)
		}),
		grpc.WithTimeout(time.Second),
	)
}
<commit_msg>Support new version in OnDefineDomain<commit_after>/*
 * This file is part of the KubeVirt project
 *
 * 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.
 *
 * Copyright 2018 Red Hat, Inc.
 *
 */

package hooks

import (
	"context"
	"encoding/json"
	"encoding/xml"
	"fmt"
	"io/ioutil"
	"net"
	"sort"
	"strings"
	"sync"
	"time"

	"google.golang.org/grpc"

	v1 "kubevirt.io/kubevirt/pkg/api/v1"
	hooksInfo "kubevirt.io/kubevirt/pkg/hooks/info"
	hooksV1alpha1 "kubevirt.io/kubevirt/pkg/hooks/v1alpha1"
	hooksV1alpha2 "kubevirt.io/kubevirt/pkg/hooks/v1alpha2"
	"kubevirt.io/kubevirt/pkg/log"
	virtwrapApi "kubevirt.io/kubevirt/pkg/virt-launcher/virtwrap/api"
)

type callBackClient struct {
	SocketPath          string
	Version             string
	subsribedHookPoints []*hooksInfo.HookPoint
}

var manager *Manager
var once sync.Once

type Manager struct {
	callbacksPerHookPoint map[string][]*callBackClient
}

func GetManager() *Manager {
	once.Do(func() {
		manager = &Manager{callbacksPerHookPoint: make(map[string][]*callBackClient)}
	})
	return manager
}

func (m *Manager) Collect(numberOfRequestedHookSidecars uint, timeout time.Duration) error {
	callbacksPerHookPoint, err := collectSideCarSockets(numberOfRequestedHookSidecars, timeout)
	if err != nil {
		return err
	}
	log.Log.Info("Collected all requested hook sidecar sockets")

	sortCallbacksPerHookPoint(callbacksPerHookPoint)
	log.Log.Infof("Sorted all collected sidecar sockets per hook point based on their priority and name: %v", callbacksPerHookPoint)

	m.callbacksPerHookPoint = callbacksPerHookPoint

	return nil
}

// TODO: Handle sockets in parallel, when a socket appears, run a goroutine trying to read Info from it
func collectSideCarSockets(numberOfRequestedHookSidecars uint, timeout time.Duration) (map[string][]*callBackClient, error) {
	callbacksPerHookPoint := make(map[string][]*callBackClient)
	processedSockets := make(map[string]bool)

	timeoutCh := time.After(timeout)

	for uint(len(processedSockets)) < numberOfRequestedHookSidecars {
		sockets, err := ioutil.ReadDir(HookSocketsSharedDirectory)
		if err != nil {
			return nil, err
		}

		for _, socket := range sockets {
			select {
			case <-timeoutCh:
				return nil, fmt.Errorf("Failed to collect all expected sidecar hook sockets within given timeout")
			default:
				if _, processed := processedSockets[socket.Name()]; processed {
					continue
				}

				callBackClient, notReady, err := processSideCarSocket(HookSocketsSharedDirectory + "/" + socket.Name())
				if notReady {
					log.Log.Info("Sidecar server might not be ready yet, retrying in the next iteration")
					continue
				} else if err != nil {
					log.Log.Reason(err).Infof("Failed to process sidecar socket: %s", socket.Name())
					return nil, err
				}

				for _, subsribedHookPoint := range callBackClient.subsribedHookPoints {
					callbacksPerHookPoint[subsribedHookPoint.GetName()] = append(callbacksPerHookPoint[subsribedHookPoint.GetName()], callBackClient)
				}

				processedSockets[socket.Name()] = true
			}
		}

		time.Sleep(time.Second)
	}

	return callbacksPerHookPoint, nil
}

func processSideCarSocket(socketPath string) (*callBackClient, bool, error) {
	conn, err := dialSocket(socketPath)
	if err != nil {
		log.Log.Reason(err).Infof("Failed to Dial hook socket: %s", socketPath)
		return nil, true, nil
	}
	defer conn.Close()

	infoClient := hooksInfo.NewInfoClient(conn)
	ctx, cancel := context.WithTimeout(context.Background(), time.Second)
	defer cancel()
	info, err := infoClient.Info(ctx, &hooksInfo.InfoParams{})
	if err != nil {
		return nil, false, err
	}

	versionsSet := make(map[string]bool)
	for _, version := range info.GetVersions() {
		versionsSet[version] = true
	}

	if _, found := versionsSet[hooksV1alpha2.Version]; found {
		return &callBackClient{
			SocketPath:          socketPath,
			Version:             hooksV1alpha2.Version,
			subsribedHookPoints: info.GetHookPoints(),
		}, false, nil
	} else if _, found := versionsSet[hooksV1alpha1.Version]; found {
		return &callBackClient{
			SocketPath:          socketPath,
			Version:             hooksV1alpha1.Version,
			subsribedHookPoints: info.GetHookPoints(),
		}, false, nil
	} else {
		return nil, false,
			fmt.Errorf("Hook sidecar does not expose a supported version. Exposed versions: %v, supported versions: %v",
				info.GetVersions(), []string{hooksV1alpha1.Version, hooksV1alpha2.Version})
	}
}

func sortCallbacksPerHookPoint(callbacksPerHookPoint map[string][]*callBackClient) {
	for _, callbacks := range callbacksPerHookPoint {
		for _, callback := range callbacks {
			sort.Slice(callbacks, func(i, j int) bool {
				if callback.subsribedHookPoints[i].Priority == callback.subsribedHookPoints[j].Priority {
					return strings.Compare(callback.subsribedHookPoints[i].Name, callback.subsribedHookPoints[j].Name) < 0
				} else {
					return callback.subsribedHookPoints[i].Priority > callback.subsribedHookPoints[j].Priority
				}
			})
		}
	}
}

func (m *Manager) OnDefineDomain(domainSpec *virtwrapApi.DomainSpec, vmi *v1.VirtualMachineInstance) (string, error) {
	domainSpecXML, err := xml.Marshal(domainSpec)
	if err != nil {
		return "", fmt.Errorf("Failed to marshal domain spec: %v", domainSpec)
	}
	if callbacks, found := m.callbacksPerHookPoint[hooksInfo.OnDefineDomainHookPointName]; found {
		for _, callback := range callbacks {
			if callback.Version == hooksV1alpha1.Version || callback.Version == hooksV1alpha2.Version {

				vmiJSON, err := json.Marshal(vmi)
				if err != nil {
					return "", fmt.Errorf("Failed to marshal VMI spec: %v", vmi)
				}

				conn, err := dialSocket(callback.SocketPath)
				if err != nil {
					log.Log.Reason(err).Infof("Failed to Dial hook socket: %s", callback.SocketPath)
					return "", err
				}
				defer conn.Close()

				client := hooksV1alpha1.NewCallbacksClient(conn)

				ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
				defer cancel()
				result, err := client.OnDefineDomain(ctx, &hooksV1alpha1.OnDefineDomainParams{
					DomainXML: domainSpecXML,
					Vmi:       vmiJSON,
				})
				if err != nil {
					return "", err
				}
				domainSpecXML = result.GetDomainXML()
			} else {
				panic("Should never happen, version compatibility check is done during Info call")
			}
		}
	}
	return string(domainSpecXML), nil
}

func (m *Manager) PreCloudInitIso(vmi *v1.VirtualMachineInstance, cloudInitData *v1.CloudInitNoCloudSource) (*v1.CloudInitNoCloudSource, error) {
	if callbacks, found := m.callbacksPerHookPoint[hooksInfo.PreCloudInitIsoHookPointName]; found {
		for _, callback := range callbacks {
			if callback.Version == hooksV1alpha2.Version {
				var resultSource *v1.CloudInitNoCloudSource
				vmiJSON, err := json.Marshal(vmi)
				if err != nil {
					return cloudInitData, fmt.Errorf("Failed to marshal VMI spec: %v", vmi)
				}

				cloudInitDataJSON, err := json.Marshal(cloudInitData)
				if err != nil {
					return cloudInitData, fmt.Errorf("Failed to marshal CloudInitNoCloudSource: %v", cloudInitData)
				}

				conn, err := dialSocket(callback.SocketPath)
				if err != nil {
					log.Log.Reason(err).Infof("Failed to Dial hook socket: %s", callback.SocketPath)
					return cloudInitData, err
				}
				defer conn.Close()

				client := hooksV1alpha2.NewCallbacksClient(conn)
				ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
				defer cancel()
				result, err := client.PreCloudInitIso(ctx, &hooksV1alpha2.PreCloudInitIsoParams{
					CloudInitData: cloudInitDataJSON,
					Vmi:           vmiJSON,
				})
				if err != nil {
					return cloudInitData, err
				}

				err = json.Unmarshal(result.GetCloudInitData(), &resultSource)
				if err != nil {
					return cloudInitData, err
				}
				return resultSource, nil
			} else {
				panic("Should never happen, version compatibility check is done during Info call")
			}
		}
	}
	return cloudInitData, nil
}

func dialSocket(socketPath string) (*grpc.ClientConn, error) {
	return grpc.Dial(
		socketPath,
		grpc.WithInsecure(),
		grpc.WithDialer(func(addr string, timeout time.Duration) (net.Conn, error) {
			return net.DialTimeout("unix", addr, timeout)
		}),
		grpc.WithTimeout(time.Second),
	)
}
<|endoftext|>


<commit_before>package paxos

import (
	"log"
	"os"

	"junta/store"
	"junta/util"
)

type result struct {
	seqn uint64
	v    string
}

type instReq struct {
	seqn uint64 // 0 means to fill in a fresh seqn
	ch   chan *instance
}

type Manager struct {
	st      *store.Store
	rg      *Registrar
	learned chan result
	reqs    chan *instReq
	logger  *log.Logger
	Self    string
	alpha   int
}

func NewManager(self string, start uint64, alpha int, st *store.Store, outs PutterTo) *Manager {
	m := &Manager{
		st:      st,
		rg:      NewRegistrar(st, start, alpha),
		learned: make(chan result),
		reqs:    make(chan *instReq),
		logger:  util.NewLogger("manager"),
		Self:    self,
		alpha:   alpha,
	}

	go m.process(start+uint64(alpha), outs)

	return m
}

func (m *Manager) Alpha() int {
	return m.alpha
}

func (m *Manager) process(next uint64, outs PutterTo) {
	instances := make(map[uint64]*instance)
	for req := range m.reqs {
		if req.seqn == 0 {
			req.seqn = next
		}
		inst, ok := instances[req.seqn]
		if !ok {
			inst = newInstance()
			go func() {
				ms, active := m.rg.setsForSeqn(req.seqn)
				m.logger.Logf("cluster %d has %d members and %d active", req.seqn, len(ms), len(active))
				m.logger.Logf("  members: %v", ms)
				m.logger.Logf("  active: %v", active)
				inst.setCluster(newCluster(m.Self, ms, active, putToWrapper{req.seqn, outs}))
			}()
			instances[req.seqn] = inst
			go func() {
				m.learned <- result{req.seqn, inst.Value()}
			}()
		}
		req.ch <- inst
		if req.seqn >= next {
			next = req.seqn + 1
		}
	}
}

func (m *Manager) getInstance(seqn uint64) (uint64, *instance) {
	r := &instReq{seqn, make(chan *instance)}
	m.reqs <- r
	it := <-r.ch
	return r.seqn, it
}

func (m *Manager) Put(msg Msg) {
	if !msg.Ok() {
		return
	}
	_, it := m.getInstance(msg.Seqn())
	it.Put(msg)
}

func (m *Manager) PutFrom(addr string, msg Msg) {
	_, it := m.getInstance(msg.Seqn())
	msg.SetFrom(it.cluster().indexByAddr(addr))
	m.Put(msg)
}

func (m *Manager) Propose(v string) (uint64, string, os.Error) {
	ch := make(chan store.Event)
	seqn, inst := m.getInstance(0)
	m.st.Wait(seqn, ch)
	m.logger.Logf("paxos propose -> %q", v)
	inst.Propose(v)
	ev := <-ch
	return seqn, ev.Mut, ev.Err
}

func (m *Manager) Recv() (uint64, string) {
	result := <-m.learned
	m.logger.Logf("paxos %d learned <- %q", result.seqn, result.v)
	return result.seqn, result.v
}
<commit_msg>properly close over seqn and instance<commit_after>package paxos

import (
	"log"
	"os"

	"junta/store"
	"junta/util"
)

type result struct {
	seqn uint64
	v    string
}

type instReq struct {
	seqn uint64 // 0 means to fill in a fresh seqn
	ch   chan *instance
}

type Manager struct {
	st      *store.Store
	rg      *Registrar
	learned chan result
	reqs    chan *instReq
	logger  *log.Logger
	Self    string
	alpha   int
	outs    PutterTo
}

func NewManager(self string, start uint64, alpha int, st *store.Store, outs PutterTo) *Manager {
	m := &Manager{
		st:      st,
		rg:      NewRegistrar(st, start, alpha),
		learned: make(chan result),
		reqs:    make(chan *instReq),
		logger:  util.NewLogger("manager"),
		Self:    self,
		alpha:   alpha,
		outs:    outs,
	}

	go m.process(start+uint64(alpha))

	return m
}

func (m *Manager) Alpha() int {
	return m.alpha
}

func (m *Manager) setCluster(seqn uint64, it *instance) {
	members, cals := m.rg.setsForSeqn(seqn)
	m.logger.Logf("cluster %d has %d members and %d cals", seqn, len(members), len(cals))
	m.logger.Logf("  members: %v", members)
	m.logger.Logf("  cals: %v", cals)
	it.setCluster(newCluster(m.Self, members, cals, putToWrapper{seqn, m.outs}))
}

func (m *Manager) process(next uint64) {
	instances := make(map[uint64]*instance)
	for req := range m.reqs {
		if req.seqn == 0 {
			req.seqn = next
		}
		inst, ok := instances[req.seqn]
		if !ok {
			inst = newInstance()
			instances[req.seqn] = inst
			go m.setCluster(req.seqn, inst)
			go func() {
				m.learned <- result{req.seqn, inst.Value()}
			}()
		}
		req.ch <- inst
		if req.seqn >= next {
			next = req.seqn + 1
		}
	}
}

func (m *Manager) getInstance(seqn uint64) (uint64, *instance) {
	r := &instReq{seqn, make(chan *instance)}
	m.reqs <- r
	it := <-r.ch
	return r.seqn, it
}

func (m *Manager) Put(msg Msg) {
	if !msg.Ok() {
		return
	}
	_, it := m.getInstance(msg.Seqn())
	it.Put(msg)
}

func (m *Manager) PutFrom(addr string, msg Msg) {
	_, it := m.getInstance(msg.Seqn())
	msg.SetFrom(it.cluster().indexByAddr(addr))
	m.Put(msg)
}

func (m *Manager) Propose(v string) (uint64, string, os.Error) {
	ch := make(chan store.Event)
	seqn, inst := m.getInstance(0)
	m.st.Wait(seqn, ch)
	m.logger.Logf("paxos propose -> %q", v)
	inst.Propose(v)
	ev := <-ch
	return seqn, ev.Mut, ev.Err
}

func (m *Manager) Recv() (uint64, string) {
	result := <-m.learned
	m.logger.Logf("paxos %d learned <- %q", result.seqn, result.v)
	return result.seqn, result.v
}
<|endoftext|>


<commit_before>package paxos

import (
	"log"
	"os"

	"junta/store"
	"junta/util"
)

const window = 50

const selfBits = 160

type result struct {
	seqn uint64
	v    string
}

type instReq struct {
	seqn uint64 // 0 means to fill in a fresh seqn
	ch   chan *instance
}

type Manager struct {
	st      *store.Store
	rg      *Registrar
	learned chan result
	reqs    chan *instReq
	logger  *log.Logger
	Self    string
}

func NewManager(start uint64, alpha int, st *store.Store, outs Putter) *Manager {
	self := util.RandHexString(selfBits)
	m := &Manager{
		st:      st,
		rg:      NewRegistrar(self, st, alpha),
		learned: make(chan result),
		reqs:    make(chan *instReq),
		logger:  util.NewLogger("manager"),
		Self:    self,
	}

	go m.process(start, outs)

	return m
}

func (m *Manager) process(next uint64, outs Putter) {
	instances := make(map[uint64]*instance)
	for req := range m.reqs {
		if req.seqn == 0 {
			req.seqn = next
		}
		inst, ok := instances[req.seqn]
		if !ok {
			// TODO find a nicer way to do this
			// This is meant to be run in a separate goroutine
			cxf := func() *cluster {
				return m.rg.clusterFor(req.seqn)
			}
			inst = newInstance(cxf, putWrapper{req.seqn, outs})
			instances[req.seqn] = inst
			go func() {
				m.learned <- result{req.seqn, inst.Value()}
			}()
		}
		req.ch <- inst
		if req.seqn >= next {
			next = req.seqn + 1
		}
	}
}

func (m *Manager) getInstance(seqn uint64) (uint64, *instance) {
	r := &instReq{seqn, make(chan *instance)}
	m.reqs <- r
	it := <-r.ch
	return r.seqn, it
}

func (m *Manager) Put(msg Msg) {
	if !msg.Ok() {
		return
	}
	_, it := m.getInstance(msg.Seqn())
	it.Put(msg)
}

func (m *Manager) PutFrom(addr string, msg Msg) {
	_, it := m.getInstance(msg.Seqn())
	msg.SetFrom(it.cluster().indexByAddr(addr))
	m.Put(msg)
}

func (m *Manager) AddrsFor(msg Msg) []string {
	_, it := m.getInstance(msg.Seqn())
	return it.cluster().addrs()
}

func (m *Manager) Propose(v string) (string, os.Error) {
	ch := make(chan store.Status)
	seqn, inst := m.getInstance(0)
	m.st.Wait(seqn, ch)
	m.logger.Logf("paxos propose -> %q", v)
	inst.Propose(v)
	status := <-ch
	return status.M, status.Err
}

func (m *Manager) Recv() (uint64, string) {
	result := <-m.learned
	m.logger.Logf("paxos %d learned <- %q", result.seqn, result.v)
	return result.seqn, result.v
}
<commit_msg>remove unused constant<commit_after>package paxos

import (
	"log"
	"os"

	"junta/store"
	"junta/util"
)

const selfBits = 160

type result struct {
	seqn uint64
	v    string
}

type instReq struct {
	seqn uint64 // 0 means to fill in a fresh seqn
	ch   chan *instance
}

type Manager struct {
	st      *store.Store
	rg      *Registrar
	learned chan result
	reqs    chan *instReq
	logger  *log.Logger
	Self    string
}

func NewManager(start uint64, alpha int, st *store.Store, outs Putter) *Manager {
	self := util.RandHexString(selfBits)
	m := &Manager{
		st:      st,
		rg:      NewRegistrar(self, st, alpha),
		learned: make(chan result),
		reqs:    make(chan *instReq),
		logger:  util.NewLogger("manager"),
		Self:    self,
	}

	go m.process(start, outs)

	return m
}

func (m *Manager) process(next uint64, outs Putter) {
	instances := make(map[uint64]*instance)
	for req := range m.reqs {
		if req.seqn == 0 {
			req.seqn = next
		}
		inst, ok := instances[req.seqn]
		if !ok {
			// TODO find a nicer way to do this
			// This is meant to be run in a separate goroutine
			cxf := func() *cluster {
				return m.rg.clusterFor(req.seqn)
			}
			inst = newInstance(cxf, putWrapper{req.seqn, outs})
			instances[req.seqn] = inst
			go func() {
				m.learned <- result{req.seqn, inst.Value()}
			}()
		}
		req.ch <- inst
		if req.seqn >= next {
			next = req.seqn + 1
		}
	}
}

func (m *Manager) getInstance(seqn uint64) (uint64, *instance) {
	r := &instReq{seqn, make(chan *instance)}
	m.reqs <- r
	it := <-r.ch
	return r.seqn, it
}

func (m *Manager) Put(msg Msg) {
	if !msg.Ok() {
		return
	}
	_, it := m.getInstance(msg.Seqn())
	it.Put(msg)
}

func (m *Manager) PutFrom(addr string, msg Msg) {
	_, it := m.getInstance(msg.Seqn())
	msg.SetFrom(it.cluster().indexByAddr(addr))
	m.Put(msg)
}

func (m *Manager) AddrsFor(msg Msg) []string {
	_, it := m.getInstance(msg.Seqn())
	return it.cluster().addrs()
}

func (m *Manager) Propose(v string) (string, os.Error) {
	ch := make(chan store.Status)
	seqn, inst := m.getInstance(0)
	m.st.Wait(seqn, ch)
	m.logger.Logf("paxos propose -> %q", v)
	inst.Propose(v)
	status := <-ch
	return status.M, status.Err
}

func (m *Manager) Recv() (uint64, string) {
	result := <-m.learned
	m.logger.Logf("paxos %d learned <- %q", result.seqn, result.v)
	return result.seqn, result.v
}
<|endoftext|>


<commit_before>package paxos

import (
	"junta/util"
	"os"
)

// In-memory format:
//
//     0      -- index of sender
//     1      -- cmd
//     2      -- flags
//     3..10   -- cluster version
//     11..17 -- seqn
//     18..   -- body -- format depends on command
//
// Wire format is same as in-memory format, but without the first byte (the
// sender index). Here it is for clarity:
//
//     0     -- cmd
//     1     -- flags
//     2..9  -- cluster version
//     10..16 -- seqn
//     17..  -- body -- format depends on command
//
// Here's how you create a `Msg` from incoming network data. This assumes you
// know some upper bound on the size of a message (for instance, UDP packets
// can't ever be more than about 1,500 bytes in practice over Ethernet).
//
//     m, addr, err := ReadMsg(conn, 3000) // plenty for an Ethernet frame
//
// Of course, you'll want to do error checking and probably fill in the `From`
// index based on the UDP sender address.
type Msg []byte

const (
	mFrom = iota
	mCmd
	mFlags
	mSeqn
	mSeqn1
	mSeqn2
	mSeqn3
	mSeqn4
	mSeqn5
	mSeqn6
	mSeqn7
	mBody
	baseLen = mBody
)

const (
	nop = iota
	invite
	rsvp
	nominate
	vote
	tick
	propose
	learn
)

// Flags
const (
	Ack = 1 << iota
)

const (
	inviteLen   = 8
	rsvpLen     = 16 // not including v
	nominateLen = 8  // not including v
	voteLen     = 8  // not including v
	tickLen     = 0
	proposeLen  = 0 // not including v
	learnLen    = 0 // not including v
)

func newInvite(crnd uint64) Msg {
	m := make(Msg, baseLen+inviteLen)
	m[mCmd] = invite
	util.Packui64(m.Body()[0:8], crnd)
	return m
}

// Returns the info for `m`. If `m` is not an invite, the result is undefined.
func inviteParts(m Msg) (crnd uint64) {
	return util.Unpackui64(m.Body())
}

func newNominate(crnd uint64, v string) Msg {
	m := make(Msg, baseLen+nominateLen+len(v))
	m[mCmd] = nominate
	util.Packui64(m.Body()[0:8], crnd)
	copy(m.Body()[nominateLen:], []byte(v))
	return m
}

// Returns the info for `m`. If `m` is not a nominate, the result is undefined.
func nominateParts(m Msg) (crnd uint64, v string) {
	crnd = util.Unpackui64(m.Body()[0:8])
	v = string(m.Body()[8:])
	return
}

func newRsvp(i, vrnd uint64, vval string) Msg {
	m := make(Msg, baseLen+rsvpLen+len(vval))
	m[mCmd] = rsvp
	util.Packui64(m.Body()[0:8], i)
	util.Packui64(m.Body()[8:16], vrnd)
	copy(m.Body()[rsvpLen:], []byte(vval))
	return m
}

// Returns the info for `m`. If `m` is not an rsvp, the result is undefined.
func rsvpParts(m Msg) (i, vrnd uint64, vval string) {
	i = util.Unpackui64(m.Body()[0:8])
	vrnd = util.Unpackui64(m.Body()[8:16])
	vval = string(m.Body()[16:])
	return
}

func newVote(i uint64, vval string) Msg {
	m := make(Msg, baseLen+voteLen+len(vval))
	m[mCmd] = vote
	util.Packui64(m.Body()[0:8], i)
	copy(m.Body()[voteLen:], []byte(vval))
	return m
}

// Returns the info for `m`. If `m` is not a vote, the result is undefined.
func voteParts(m Msg) (i uint64, vval string) {
	i = util.Unpackui64(m.Body()[0:8])
	vval = string(m.Body()[8:])
	return
}

func newTick() Msg {
	m := make(Msg, baseLen+tickLen)
	m[mCmd] = tick
	return m
}

func newPropose(val string) Msg {
	m := make(Msg, baseLen+proposeLen+len(val))
	m[mCmd] = propose
	copy(m.Body()[proposeLen:], []byte(val))
	return m
}

func newLearn(val string) Msg {
	m := make(Msg, baseLen+learnLen+len(val))
	m[mCmd] = learn
	copy(m.Body()[learnLen:], []byte(val))
	return m
}

func learnParts(m Msg) string {
	return string(m.Body())
}

// Returns the info for `m`. If `m` is not a propose, the result is undefined.
func proposeParts(m Msg) (val string) {
	val = string(m.Body()[proposeLen:])
	return
}

func (m Msg) From() int {
	return int(m[mFrom])
}

func (m Msg) Cmd() int {
	return int(m[mCmd])
}

func (m Msg) Seqn() uint64 {
	return util.Unpackui64(m[mSeqn : mSeqn+8])
}

func (m Msg) Body() []byte {
	return m[mBody:]
}

// Typically used after reading from the network, when building a new `Msg`
// object.
//
// This assumes the number of nodes fits in a byte.
func (m Msg) SetFrom(from int) {
	m[mFrom] = byte(from)
}

// Typically used just before writing `m` to the network.
func (m Msg) SetSeqn(seqn uint64) {
	util.Packui64(m[mSeqn:mSeqn+8], seqn)
}

// Check that `m` is well-formed. Does not guarantee that it will be valid or
// meaningful. If this method returns `true`, you can safely pass `m` into a
// `Putter`.
func (m Msg) Ok() bool {
	if len(m) < 2 {
		return false
	}
	switch m.Cmd() {
	case invite:
		return len(m.Body()) == inviteLen
	case rsvp:
		return len(m.Body()) >= rsvpLen
	case nominate:
		return len(m.Body()) >= nominateLen
	case vote:
		return len(m.Body()) >= voteLen
	}
	return false
}

func (m Msg) WireBytes() []byte {
	return m[mCmd:]
}

func (m *Msg) readFrom(c ReadFromer) (addr string, err os.Error) {
	n, a, er := c.ReadFrom(m.WireBytes())
	if er != nil {
		return "", er
	}
	*m = (*m)[0 : n+1] // truncate to fit
	return a.String(), nil
}

func ReadMsg(c ReadFromer, bound int) (m Msg, addr string, err os.Error) {
	m = make(Msg, bound)
	addr, err = m.readFrom(c)
	return
}

func (m *Msg) HasFlags(flags int) bool {
	return (*m)[mFlags] & byte(flags) != 0
}

func (m *Msg) SetFlags(flags int) Msg {
	(*m)[mFlags] |= byte(flags)
	return *m
}

func (m *Msg) ClearFlags(flags int) Msg {
	(*m)[mFlags] &= ^byte(flags)
	return *m
}

func (m *Msg) Dup() Msg {
	o := make(Msg, len(*m))
	copy(o, *m)
	return o
}
<commit_msg>these don't need a pointer receiver<commit_after>package paxos

import (
	"junta/util"
	"os"
)

// In-memory format:
//
//     0      -- index of sender
//     1      -- cmd
//     2      -- flags
//     3..10   -- cluster version
//     11..17 -- seqn
//     18..   -- body -- format depends on command
//
// Wire format is same as in-memory format, but without the first byte (the
// sender index). Here it is for clarity:
//
//     0     -- cmd
//     1     -- flags
//     2..9  -- cluster version
//     10..16 -- seqn
//     17..  -- body -- format depends on command
//
// Here's how you create a `Msg` from incoming network data. This assumes you
// know some upper bound on the size of a message (for instance, UDP packets
// can't ever be more than about 1,500 bytes in practice over Ethernet).
//
//     m, addr, err := ReadMsg(conn, 3000) // plenty for an Ethernet frame
//
// Of course, you'll want to do error checking and probably fill in the `From`
// index based on the UDP sender address.
type Msg []byte

const (
	mFrom = iota
	mCmd
	mFlags
	mSeqn
	mSeqn1
	mSeqn2
	mSeqn3
	mSeqn4
	mSeqn5
	mSeqn6
	mSeqn7
	mBody
	baseLen = mBody
)

const (
	nop = iota
	invite
	rsvp
	nominate
	vote
	tick
	propose
	learn
)

// Flags
const (
	Ack = 1 << iota
)

const (
	inviteLen   = 8
	rsvpLen     = 16 // not including v
	nominateLen = 8  // not including v
	voteLen     = 8  // not including v
	tickLen     = 0
	proposeLen  = 0 // not including v
	learnLen    = 0 // not including v
)

func newInvite(crnd uint64) Msg {
	m := make(Msg, baseLen+inviteLen)
	m[mCmd] = invite
	util.Packui64(m.Body()[0:8], crnd)
	return m
}

// Returns the info for `m`. If `m` is not an invite, the result is undefined.
func inviteParts(m Msg) (crnd uint64) {
	return util.Unpackui64(m.Body())
}

func newNominate(crnd uint64, v string) Msg {
	m := make(Msg, baseLen+nominateLen+len(v))
	m[mCmd] = nominate
	util.Packui64(m.Body()[0:8], crnd)
	copy(m.Body()[nominateLen:], []byte(v))
	return m
}

// Returns the info for `m`. If `m` is not a nominate, the result is undefined.
func nominateParts(m Msg) (crnd uint64, v string) {
	crnd = util.Unpackui64(m.Body()[0:8])
	v = string(m.Body()[8:])
	return
}

func newRsvp(i, vrnd uint64, vval string) Msg {
	m := make(Msg, baseLen+rsvpLen+len(vval))
	m[mCmd] = rsvp
	util.Packui64(m.Body()[0:8], i)
	util.Packui64(m.Body()[8:16], vrnd)
	copy(m.Body()[rsvpLen:], []byte(vval))
	return m
}

// Returns the info for `m`. If `m` is not an rsvp, the result is undefined.
func rsvpParts(m Msg) (i, vrnd uint64, vval string) {
	i = util.Unpackui64(m.Body()[0:8])
	vrnd = util.Unpackui64(m.Body()[8:16])
	vval = string(m.Body()[16:])
	return
}

func newVote(i uint64, vval string) Msg {
	m := make(Msg, baseLen+voteLen+len(vval))
	m[mCmd] = vote
	util.Packui64(m.Body()[0:8], i)
	copy(m.Body()[voteLen:], []byte(vval))
	return m
}

// Returns the info for `m`. If `m` is not a vote, the result is undefined.
func voteParts(m Msg) (i uint64, vval string) {
	i = util.Unpackui64(m.Body()[0:8])
	vval = string(m.Body()[8:])
	return
}

func newTick() Msg {
	m := make(Msg, baseLen+tickLen)
	m[mCmd] = tick
	return m
}

func newPropose(val string) Msg {
	m := make(Msg, baseLen+proposeLen+len(val))
	m[mCmd] = propose
	copy(m.Body()[proposeLen:], []byte(val))
	return m
}

func newLearn(val string) Msg {
	m := make(Msg, baseLen+learnLen+len(val))
	m[mCmd] = learn
	copy(m.Body()[learnLen:], []byte(val))
	return m
}

func learnParts(m Msg) string {
	return string(m.Body())
}

// Returns the info for `m`. If `m` is not a propose, the result is undefined.
func proposeParts(m Msg) (val string) {
	val = string(m.Body()[proposeLen:])
	return
}

func (m Msg) From() int {
	return int(m[mFrom])
}

func (m Msg) Cmd() int {
	return int(m[mCmd])
}

func (m Msg) Seqn() uint64 {
	return util.Unpackui64(m[mSeqn : mSeqn+8])
}

func (m Msg) Body() []byte {
	return m[mBody:]
}

// Typically used after reading from the network, when building a new `Msg`
// object.
//
// This assumes the number of nodes fits in a byte.
func (m Msg) SetFrom(from int) {
	m[mFrom] = byte(from)
}

// Typically used just before writing `m` to the network.
func (m Msg) SetSeqn(seqn uint64) {
	util.Packui64(m[mSeqn:mSeqn+8], seqn)
}

// Check that `m` is well-formed. Does not guarantee that it will be valid or
// meaningful. If this method returns `true`, you can safely pass `m` into a
// `Putter`.
func (m Msg) Ok() bool {
	if len(m) < 2 {
		return false
	}
	switch m.Cmd() {
	case invite:
		return len(m.Body()) == inviteLen
	case rsvp:
		return len(m.Body()) >= rsvpLen
	case nominate:
		return len(m.Body()) >= nominateLen
	case vote:
		return len(m.Body()) >= voteLen
	}
	return false
}

func (m Msg) WireBytes() []byte {
	return m[mCmd:]
}

func (m *Msg) readFrom(c ReadFromer) (addr string, err os.Error) {
	n, a, er := c.ReadFrom(m.WireBytes())
	if er != nil {
		return "", er
	}
	*m = (*m)[0 : n+1] // truncate to fit
	return a.String(), nil
}

func ReadMsg(c ReadFromer, bound int) (m Msg, addr string, err os.Error) {
	m = make(Msg, bound)
	addr, err = m.readFrom(c)
	return
}

func (m Msg) HasFlags(flags int) bool {
	return m[mFlags] & byte(flags) != 0
}

func (m Msg) SetFlags(flags int) Msg {
	m[mFlags] |= byte(flags)
	return m
}

func (m Msg) ClearFlags(flags int) Msg {
	m[mFlags] &= ^byte(flags)
	return m
}

func (m *Msg) Dup() Msg {
	o := make(Msg, len(*m))
	copy(o, *m)
	return o
}
<|endoftext|>


<commit_before>/*
Copyright 2017 The Kubernetes Authors.

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

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

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

package server

import (
	"time"

	eventtypes "github.com/containerd/containerd/api/events"
	containerdio "github.com/containerd/containerd/cio"
	"github.com/containerd/containerd/errdefs"
	"github.com/containerd/containerd/events"
	"github.com/containerd/typeurl"
	gogotypes "github.com/gogo/protobuf/types"
	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"
	"golang.org/x/net/context"
	"k8s.io/apimachinery/pkg/util/clock"

	ctrdutil "github.com/containerd/cri/pkg/containerd/util"
	"github.com/containerd/cri/pkg/store"
	containerstore "github.com/containerd/cri/pkg/store/container"
	sandboxstore "github.com/containerd/cri/pkg/store/sandbox"
)

const (
	backOffInitDuration        = 1 * time.Second
	backOffMaxDuration         = 5 * time.Minute
	backOffExpireCheckDuration = 1 * time.Second
)

// eventMonitor monitors containerd event and updates internal state correspondingly.
// TODO(random-liu): [P1] Figure out is it possible to drop event during containerd
// is running. If it is, we should do periodically list to sync state with containerd.
type eventMonitor struct {
	containerStore *containerstore.Store
	sandboxStore   *sandboxstore.Store
	ch             <-chan *events.Envelope
	errCh          <-chan error
	ctx            context.Context
	cancel         context.CancelFunc
	backOff        *backOff
}

type backOff struct {
	queuePool     map[string]*backOffQueue
	ticker        *time.Ticker
	minDuration   time.Duration
	maxDuration   time.Duration
	checkDuration time.Duration
	clock         clock.Clock
}

type backOffQueue struct {
	events     []interface{}
	expireTime time.Time
	duration   time.Duration
	clock      clock.Clock
}

// Create new event monitor. New event monitor will start subscribing containerd event. All events
// happen after it should be monitored.
func newEventMonitor(c *containerstore.Store, s *sandboxstore.Store) *eventMonitor {
	// event subscribe doesn't need namespace.
	ctx, cancel := context.WithCancel(context.Background())
	return &eventMonitor{
		containerStore: c,
		sandboxStore:   s,
		ctx:            ctx,
		cancel:         cancel,
		backOff:        newBackOff(),
	}
}

// subscribe starts to subscribe containerd events.
func (em *eventMonitor) subscribe(subscriber events.Subscriber) {
	filters := []string{
		`topic=="/tasks/exit"`,
		`topic=="/tasks/oom"`,
	}
	em.ch, em.errCh = subscriber.Subscribe(em.ctx, filters...)
}

func convertEvent(e *gogotypes.Any) (string, interface{}, error) {
	containerID := ""
	evt, err := typeurl.UnmarshalAny(e)
	if err != nil {
		return "", nil, errors.Wrap(err, "failed to unmarshalany")
	}

	switch evt.(type) {
	case *eventtypes.TaskExit:
		containerID = evt.(*eventtypes.TaskExit).ContainerID
	case *eventtypes.TaskOOM:
		containerID = evt.(*eventtypes.TaskOOM).ContainerID
	default:
		return "", nil, errors.New("unsupported event")
	}
	return containerID, evt, nil
}

// start starts the event monitor which monitors and handles all container events. It returns
// a channel for the caller to wait for the event monitor to stop. start must be called after
// subscribe.
func (em *eventMonitor) start() (<-chan struct{}, error) {
	if em.ch == nil || em.errCh == nil {
		return nil, errors.New("event channel is nil")
	}
	closeCh := make(chan struct{})
	go func() {
		backOffCheckCh := em.backOff.start()
		for {
			select {
			case e := <-em.ch:
				logrus.Debugf("Received containerd event timestamp - %v, namespace - %q, topic - %q", e.Timestamp, e.Namespace, e.Topic)
				cID, evt, err := convertEvent(e.Event)
				if err != nil {
					logrus.WithError(err).Errorf("Failed to convert event %+v", e)
					break
				}
				if em.backOff.isInBackOff(cID) {
					logrus.Infof("Events for container %q is in backoff, enqueue event %+v", cID, evt)
					em.backOff.enBackOff(cID, evt)
					break
				}
				if err := em.handleEvent(evt); err != nil {
					logrus.WithError(err).Errorf("Failed to handle event %+v for container %s", evt, cID)
					em.backOff.enBackOff(cID, evt)
				}
			case err := <-em.errCh:
				logrus.WithError(err).Error("Failed to handle event stream")
				close(closeCh)
				return
			case <-backOffCheckCh:
				cIDs := em.backOff.getExpiredContainers()
				for _, cID := range cIDs {
					queue := em.backOff.deBackOff(cID)
					for i, any := range queue.events {
						if err := em.handleEvent(any); err != nil {
							logrus.WithError(err).Errorf("Failed to handle backOff event %+v for container %s", any, cID)
							em.backOff.reBackOff(cID, queue.events[i:], queue.duration)
							break
						}
					}
				}
			}
		}
	}()
	return closeCh, nil
}

// stop stops the event monitor. It will close the event channel.
// Once event monitor is stopped, it can't be started.
func (em *eventMonitor) stop() {
	em.backOff.stop()
	em.cancel()
}

// handleEvent handles a containerd event.
func (em *eventMonitor) handleEvent(any interface{}) error {
	ctx := ctrdutil.NamespacedContext()
	switch any.(type) {
	// If containerd-shim exits unexpectedly, there will be no corresponding event.
	// However, containerd could not retrieve container state in that case, so it's
	// fine to leave out that case for now.
	// TODO(random-liu): [P2] Handle containerd-shim exit.
	case *eventtypes.TaskExit:
		e := any.(*eventtypes.TaskExit)
		cntr, err := em.containerStore.Get(e.ContainerID)
		if err == nil {
			if err := handleContainerExit(ctx, e, cntr); err != nil {
				return errors.Wrap(err, "failed to handle container TaskExit event")
			}
			return nil
		} else if err != store.ErrNotExist {
			return errors.Wrap(err, "can't find container for TaskExit event")
		}
		// Use GetAll to include sandbox in unknown state.
		sb, err := em.sandboxStore.GetAll(e.ContainerID)
		if err == nil {
			if err := handleSandboxExit(ctx, e, sb); err != nil {
				return errors.Wrap(err, "failed to handle sandbox TaskExit event")
			}
			return nil
		} else if err != store.ErrNotExist {
			return errors.Wrap(err, "can't find sandbox for TaskExit event")
		}
		return nil
	case *eventtypes.TaskOOM:
		e := any.(*eventtypes.TaskOOM)
		logrus.Infof("TaskOOM event %+v", e)
		cntr, err := em.containerStore.Get(e.ContainerID)
		if err != nil {
			if err != store.ErrNotExist {
				return errors.Wrap(err, "can't find container for TaskOOM event")
			}
			if _, err = em.sandboxStore.Get(e.ContainerID); err != nil {
				if err != store.ErrNotExist {
					return errors.Wrap(err, "can't find sandbox for TaskOOM event")
				}
				return nil
			}
			return nil
		}
		err = cntr.Status.UpdateSync(func(status containerstore.Status) (containerstore.Status, error) {
			status.Reason = oomExitReason
			return status, nil
		})
		if err != nil {
			return errors.Wrap(err, "failed to update container status for TaskOOM event")
		}
	}

	return nil
}

// handleContainerExit handles TaskExit event for container.
func handleContainerExit(ctx context.Context, e *eventtypes.TaskExit, cntr containerstore.Container) error {
	if e.Pid != cntr.Status.Get().Pid {
		// Non-init process died, ignore the event.
		return nil
	}
	// Attach container IO so that `Delete` could cleanup the stream properly.
	task, err := cntr.Container.Task(ctx,
		func(*containerdio.FIFOSet) (containerdio.IO, error) {
			return cntr.IO, nil
		},
	)
	if err != nil {
		if !errdefs.IsNotFound(err) {
			return errors.Wrapf(err, "failed to load task for container")
		}
	} else {
		// TODO(random-liu): [P1] This may block the loop, we may want to spawn a worker
		if _, err = task.Delete(ctx); err != nil {
			if !errdefs.IsNotFound(err) {
				return errors.Wrap(err, "failed to stop container")
			}
			// Move on to make sure container status is updated.
		}
	}
	err = cntr.Status.UpdateSync(func(status containerstore.Status) (containerstore.Status, error) {
		// If FinishedAt has been set (e.g. with start failure), keep as
		// it is.
		if status.FinishedAt != 0 {
			return status, nil
		}
		status.Pid = 0
		status.FinishedAt = e.ExitedAt.UnixNano()
		status.ExitCode = int32(e.ExitStatus)
		return status, nil
	})
	if err != nil {
		return errors.Wrap(err, "failed to update container state")
	}
	// Using channel to propagate the information of container stop
	cntr.Stop()
	return nil
}

// handleSandboxExit handles TaskExit event for sandbox.
func handleSandboxExit(ctx context.Context, e *eventtypes.TaskExit, sb sandboxstore.Sandbox) error {
	if e.Pid != sb.Status.Get().Pid {
		// Non-init process died, ignore the event.
		return nil
	}
	// No stream attached to sandbox container.
	task, err := sb.Container.Task(ctx, nil)
	if err != nil {
		if !errdefs.IsNotFound(err) {
			return errors.Wrap(err, "failed to load task for sandbox")
		}
	} else {
		// TODO(random-liu): [P1] This may block the loop, we may want to spawn a worker
		if _, err = task.Delete(ctx); err != nil {
			if !errdefs.IsNotFound(err) {
				return errors.Wrap(err, "failed to stop sandbox")
			}
			// Move on to make sure container status is updated.
		}
	}
	err = sb.Status.Update(func(status sandboxstore.Status) (sandboxstore.Status, error) {
		// NOTE(random-liu): We SHOULD NOT change UNKNOWN state here.
		// If sandbox state is UNKNOWN when event monitor receives an TaskExit event,
		// it means that sandbox start has failed. In that case, `RunPodSandbox` will
		// cleanup everything immediately.
		// Once sandbox state goes out of UNKNOWN, it becomes visable to the user, which
		// is not what we want.
		if status.State != sandboxstore.StateUnknown {
			status.State = sandboxstore.StateNotReady
		}
		status.Pid = 0
		return status, nil
	})
	if err != nil {
		return errors.Wrap(err, "failed to update sandbox state")
	}
	// Using channel to propagate the information of sandbox stop
	sb.Stop()
	return nil
}

func newBackOff() *backOff {
	return &backOff{
		queuePool:     map[string]*backOffQueue{},
		minDuration:   backOffInitDuration,
		maxDuration:   backOffMaxDuration,
		checkDuration: backOffExpireCheckDuration,
		clock:         clock.RealClock{},
	}
}

func (b *backOff) getExpiredContainers() []string {
	var containers []string
	for c, q := range b.queuePool {
		if q.isExpire() {
			containers = append(containers, c)
		}
	}
	return containers
}

func (b *backOff) isInBackOff(key string) bool {
	if _, ok := b.queuePool[key]; ok {
		return true
	}
	return false
}

// enBackOff start to backOff and put event to the tail of queue
func (b *backOff) enBackOff(key string, evt interface{}) {
	if queue, ok := b.queuePool[key]; ok {
		queue.events = append(queue.events, evt)
		return
	}
	b.queuePool[key] = newBackOffQueue([]interface{}{evt}, b.minDuration, b.clock)
}

// enBackOff get out the whole queue
func (b *backOff) deBackOff(key string) *backOffQueue {
	queue := b.queuePool[key]
	delete(b.queuePool, key)
	return queue
}

// enBackOff start to backOff again and put events to the queue
func (b *backOff) reBackOff(key string, events []interface{}, oldDuration time.Duration) {
	duration := 2 * oldDuration
	if duration > b.maxDuration {
		duration = b.maxDuration
	}
	b.queuePool[key] = newBackOffQueue(events, duration, b.clock)
}

func (b *backOff) start() <-chan time.Time {
	b.ticker = time.NewTicker(b.checkDuration)
	return b.ticker.C
}

func (b *backOff) stop() {
	b.ticker.Stop()
}

func newBackOffQueue(events []interface{}, init time.Duration, c clock.Clock) *backOffQueue {
	return &backOffQueue{
		events:     events,
		duration:   init,
		expireTime: c.Now().Add(init),
		clock:      c,
	}
}

func (q *backOffQueue) isExpire() bool {
	// return time.Now >= expireTime
	return !q.clock.Now().Before(q.expireTime)
}
<commit_msg>Fix event monitor panic.<commit_after>/*
Copyright 2017 The Kubernetes Authors.

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

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

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

package server

import (
	"sync"
	"time"

	eventtypes "github.com/containerd/containerd/api/events"
	containerdio "github.com/containerd/containerd/cio"
	"github.com/containerd/containerd/errdefs"
	"github.com/containerd/containerd/events"
	"github.com/containerd/typeurl"
	gogotypes "github.com/gogo/protobuf/types"
	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"
	"golang.org/x/net/context"
	"k8s.io/apimachinery/pkg/util/clock"

	ctrdutil "github.com/containerd/cri/pkg/containerd/util"
	"github.com/containerd/cri/pkg/store"
	containerstore "github.com/containerd/cri/pkg/store/container"
	sandboxstore "github.com/containerd/cri/pkg/store/sandbox"
)

const (
	backOffInitDuration        = 1 * time.Second
	backOffMaxDuration         = 5 * time.Minute
	backOffExpireCheckDuration = 1 * time.Second
)

// eventMonitor monitors containerd event and updates internal state correspondingly.
// TODO(random-liu): [P1] Figure out is it possible to drop event during containerd
// is running. If it is, we should do periodically list to sync state with containerd.
type eventMonitor struct {
	containerStore *containerstore.Store
	sandboxStore   *sandboxstore.Store
	ch             <-chan *events.Envelope
	errCh          <-chan error
	ctx            context.Context
	cancel         context.CancelFunc
	backOff        *backOff
}

type backOff struct {
	queuePool map[string]*backOffQueue
	// tickerMu is mutex used to protect the ticker.
	tickerMu      sync.Mutex
	ticker        *time.Ticker
	minDuration   time.Duration
	maxDuration   time.Duration
	checkDuration time.Duration
	clock         clock.Clock
}

type backOffQueue struct {
	events     []interface{}
	expireTime time.Time
	duration   time.Duration
	clock      clock.Clock
}

// Create new event monitor. New event monitor will start subscribing containerd event. All events
// happen after it should be monitored.
func newEventMonitor(c *containerstore.Store, s *sandboxstore.Store) *eventMonitor {
	// event subscribe doesn't need namespace.
	ctx, cancel := context.WithCancel(context.Background())
	return &eventMonitor{
		containerStore: c,
		sandboxStore:   s,
		ctx:            ctx,
		cancel:         cancel,
		backOff:        newBackOff(),
	}
}

// subscribe starts to subscribe containerd events.
func (em *eventMonitor) subscribe(subscriber events.Subscriber) {
	filters := []string{
		`topic=="/tasks/exit"`,
		`topic=="/tasks/oom"`,
	}
	em.ch, em.errCh = subscriber.Subscribe(em.ctx, filters...)
}

func convertEvent(e *gogotypes.Any) (string, interface{}, error) {
	containerID := ""
	evt, err := typeurl.UnmarshalAny(e)
	if err != nil {
		return "", nil, errors.Wrap(err, "failed to unmarshalany")
	}

	switch evt.(type) {
	case *eventtypes.TaskExit:
		containerID = evt.(*eventtypes.TaskExit).ContainerID
	case *eventtypes.TaskOOM:
		containerID = evt.(*eventtypes.TaskOOM).ContainerID
	default:
		return "", nil, errors.New("unsupported event")
	}
	return containerID, evt, nil
}

// start starts the event monitor which monitors and handles all container events. It returns
// a channel for the caller to wait for the event monitor to stop. start must be called after
// subscribe.
func (em *eventMonitor) start() (<-chan struct{}, error) {
	if em.ch == nil || em.errCh == nil {
		return nil, errors.New("event channel is nil")
	}
	closeCh := make(chan struct{})
	backOffCheckCh := em.backOff.start()
	go func() {
		for {
			select {
			case e := <-em.ch:
				logrus.Debugf("Received containerd event timestamp - %v, namespace - %q, topic - %q", e.Timestamp, e.Namespace, e.Topic)
				cID, evt, err := convertEvent(e.Event)
				if err != nil {
					logrus.WithError(err).Errorf("Failed to convert event %+v", e)
					break
				}
				if em.backOff.isInBackOff(cID) {
					logrus.Infof("Events for container %q is in backoff, enqueue event %+v", cID, evt)
					em.backOff.enBackOff(cID, evt)
					break
				}
				if err := em.handleEvent(evt); err != nil {
					logrus.WithError(err).Errorf("Failed to handle event %+v for container %s", evt, cID)
					em.backOff.enBackOff(cID, evt)
				}
			case err := <-em.errCh:
				logrus.WithError(err).Error("Failed to handle event stream")
				close(closeCh)
				return
			case <-backOffCheckCh:
				cIDs := em.backOff.getExpiredContainers()
				for _, cID := range cIDs {
					queue := em.backOff.deBackOff(cID)
					for i, any := range queue.events {
						if err := em.handleEvent(any); err != nil {
							logrus.WithError(err).Errorf("Failed to handle backOff event %+v for container %s", any, cID)
							em.backOff.reBackOff(cID, queue.events[i:], queue.duration)
							break
						}
					}
				}
			}
		}
	}()
	return closeCh, nil
}

// stop stops the event monitor. It will close the event channel.
// Once event monitor is stopped, it can't be started.
func (em *eventMonitor) stop() {
	em.backOff.stop()
	em.cancel()
}

// handleEvent handles a containerd event.
func (em *eventMonitor) handleEvent(any interface{}) error {
	ctx := ctrdutil.NamespacedContext()
	switch any.(type) {
	// If containerd-shim exits unexpectedly, there will be no corresponding event.
	// However, containerd could not retrieve container state in that case, so it's
	// fine to leave out that case for now.
	// TODO(random-liu): [P2] Handle containerd-shim exit.
	case *eventtypes.TaskExit:
		e := any.(*eventtypes.TaskExit)
		cntr, err := em.containerStore.Get(e.ContainerID)
		if err == nil {
			if err := handleContainerExit(ctx, e, cntr); err != nil {
				return errors.Wrap(err, "failed to handle container TaskExit event")
			}
			return nil
		} else if err != store.ErrNotExist {
			return errors.Wrap(err, "can't find container for TaskExit event")
		}
		// Use GetAll to include sandbox in unknown state.
		sb, err := em.sandboxStore.GetAll(e.ContainerID)
		if err == nil {
			if err := handleSandboxExit(ctx, e, sb); err != nil {
				return errors.Wrap(err, "failed to handle sandbox TaskExit event")
			}
			return nil
		} else if err != store.ErrNotExist {
			return errors.Wrap(err, "can't find sandbox for TaskExit event")
		}
		return nil
	case *eventtypes.TaskOOM:
		e := any.(*eventtypes.TaskOOM)
		logrus.Infof("TaskOOM event %+v", e)
		cntr, err := em.containerStore.Get(e.ContainerID)
		if err != nil {
			if err != store.ErrNotExist {
				return errors.Wrap(err, "can't find container for TaskOOM event")
			}
			if _, err = em.sandboxStore.Get(e.ContainerID); err != nil {
				if err != store.ErrNotExist {
					return errors.Wrap(err, "can't find sandbox for TaskOOM event")
				}
				return nil
			}
			return nil
		}
		err = cntr.Status.UpdateSync(func(status containerstore.Status) (containerstore.Status, error) {
			status.Reason = oomExitReason
			return status, nil
		})
		if err != nil {
			return errors.Wrap(err, "failed to update container status for TaskOOM event")
		}
	}

	return nil
}

// handleContainerExit handles TaskExit event for container.
func handleContainerExit(ctx context.Context, e *eventtypes.TaskExit, cntr containerstore.Container) error {
	if e.Pid != cntr.Status.Get().Pid {
		// Non-init process died, ignore the event.
		return nil
	}
	// Attach container IO so that `Delete` could cleanup the stream properly.
	task, err := cntr.Container.Task(ctx,
		func(*containerdio.FIFOSet) (containerdio.IO, error) {
			return cntr.IO, nil
		},
	)
	if err != nil {
		if !errdefs.IsNotFound(err) {
			return errors.Wrapf(err, "failed to load task for container")
		}
	} else {
		// TODO(random-liu): [P1] This may block the loop, we may want to spawn a worker
		if _, err = task.Delete(ctx); err != nil {
			if !errdefs.IsNotFound(err) {
				return errors.Wrap(err, "failed to stop container")
			}
			// Move on to make sure container status is updated.
		}
	}
	err = cntr.Status.UpdateSync(func(status containerstore.Status) (containerstore.Status, error) {
		// If FinishedAt has been set (e.g. with start failure), keep as
		// it is.
		if status.FinishedAt != 0 {
			return status, nil
		}
		status.Pid = 0
		status.FinishedAt = e.ExitedAt.UnixNano()
		status.ExitCode = int32(e.ExitStatus)
		return status, nil
	})
	if err != nil {
		return errors.Wrap(err, "failed to update container state")
	}
	// Using channel to propagate the information of container stop
	cntr.Stop()
	return nil
}

// handleSandboxExit handles TaskExit event for sandbox.
func handleSandboxExit(ctx context.Context, e *eventtypes.TaskExit, sb sandboxstore.Sandbox) error {
	if e.Pid != sb.Status.Get().Pid {
		// Non-init process died, ignore the event.
		return nil
	}
	// No stream attached to sandbox container.
	task, err := sb.Container.Task(ctx, nil)
	if err != nil {
		if !errdefs.IsNotFound(err) {
			return errors.Wrap(err, "failed to load task for sandbox")
		}
	} else {
		// TODO(random-liu): [P1] This may block the loop, we may want to spawn a worker
		if _, err = task.Delete(ctx); err != nil {
			if !errdefs.IsNotFound(err) {
				return errors.Wrap(err, "failed to stop sandbox")
			}
			// Move on to make sure container status is updated.
		}
	}
	err = sb.Status.Update(func(status sandboxstore.Status) (sandboxstore.Status, error) {
		// NOTE(random-liu): We SHOULD NOT change UNKNOWN state here.
		// If sandbox state is UNKNOWN when event monitor receives an TaskExit event,
		// it means that sandbox start has failed. In that case, `RunPodSandbox` will
		// cleanup everything immediately.
		// Once sandbox state goes out of UNKNOWN, it becomes visable to the user, which
		// is not what we want.
		if status.State != sandboxstore.StateUnknown {
			status.State = sandboxstore.StateNotReady
		}
		status.Pid = 0
		return status, nil
	})
	if err != nil {
		return errors.Wrap(err, "failed to update sandbox state")
	}
	// Using channel to propagate the information of sandbox stop
	sb.Stop()
	return nil
}

func newBackOff() *backOff {
	return &backOff{
		queuePool:     map[string]*backOffQueue{},
		minDuration:   backOffInitDuration,
		maxDuration:   backOffMaxDuration,
		checkDuration: backOffExpireCheckDuration,
		clock:         clock.RealClock{},
	}
}

func (b *backOff) getExpiredContainers() []string {
	var containers []string
	for c, q := range b.queuePool {
		if q.isExpire() {
			containers = append(containers, c)
		}
	}
	return containers
}

func (b *backOff) isInBackOff(key string) bool {
	if _, ok := b.queuePool[key]; ok {
		return true
	}
	return false
}

// enBackOff start to backOff and put event to the tail of queue
func (b *backOff) enBackOff(key string, evt interface{}) {
	if queue, ok := b.queuePool[key]; ok {
		queue.events = append(queue.events, evt)
		return
	}
	b.queuePool[key] = newBackOffQueue([]interface{}{evt}, b.minDuration, b.clock)
}

// enBackOff get out the whole queue
func (b *backOff) deBackOff(key string) *backOffQueue {
	queue := b.queuePool[key]
	delete(b.queuePool, key)
	return queue
}

// enBackOff start to backOff again and put events to the queue
func (b *backOff) reBackOff(key string, events []interface{}, oldDuration time.Duration) {
	duration := 2 * oldDuration
	if duration > b.maxDuration {
		duration = b.maxDuration
	}
	b.queuePool[key] = newBackOffQueue(events, duration, b.clock)
}

func (b *backOff) start() <-chan time.Time {
	b.tickerMu.Lock()
	defer b.tickerMu.Unlock()
	b.ticker = time.NewTicker(b.checkDuration)
	return b.ticker.C
}

func (b *backOff) stop() {
	b.tickerMu.Lock()
	defer b.tickerMu.Unlock()
	if b.ticker != nil {
		b.ticker.Stop()
	}
}

func newBackOffQueue(events []interface{}, init time.Duration, c clock.Clock) *backOffQueue {
	return &backOffQueue{
		events:     events,
		duration:   init,
		expireTime: c.Now().Add(init),
		clock:      c,
	}
}

func (q *backOffQueue) isExpire() bool {
	// return time.Now >= expireTime
	return !q.clock.Now().Before(q.expireTime)
}
<|endoftext|>


<commit_before>package server

import (
	"fmt"
	"github.com/Aptomi/aptomi/pkg/config"
	"github.com/Aptomi/aptomi/pkg/external"
	"github.com/Aptomi/aptomi/pkg/external/secrets"
	"github.com/Aptomi/aptomi/pkg/external/users"
	"github.com/Aptomi/aptomi/pkg/lang"
	"github.com/Aptomi/aptomi/pkg/object"
	"github.com/Aptomi/aptomi/pkg/object/codec"
	"github.com/Aptomi/aptomi/pkg/object/codec/yaml"
	"github.com/Aptomi/aptomi/pkg/object/store/bolt"
	"github.com/Aptomi/aptomi/pkg/server/api"
	"github.com/Aptomi/aptomi/pkg/server/store"
	"github.com/Aptomi/aptomi/pkg/version"
	"github.com/Aptomi/aptomi/pkg/webui"
	"github.com/gorilla/handlers"
	"github.com/julienschmidt/httprouter"
	"net/http"
	"os"
	"time"
)

// Init http server with all handlers
// * version handler
// * api handler
// * event logs api (should it be separate?)
// * webui handler (serve static files)

// Start some go routines
// * users fetcher
// * revisions applier

// Some notes
// * in dev mode serve webui files from specified directory, otherwise serve from inside of binary

// Server is a HTTP server which serves API and UI
type Server struct {
	cfg              *config.Server
	backgroundErrors chan string
	catalog          *object.Catalog
	codec            codec.MarshallerUnmarshaller

	externalData *external.Data
	store        store.ServerStore
	httpServer   *http.Server
}

// NewServer creates a new HTTP Server
func NewServer(cfg *config.Server) *Server {
	s := &Server{
		cfg:              cfg,
		backgroundErrors: make(chan string),
	}

	s.catalog = object.NewCatalog().Append(lang.Objects...).Append(store.Objects...)
	s.codec = yaml.NewCodec(s.catalog)

	return s
}

// Start makes HTTP server start serving content
func (s *Server) Start() {
	s.initStore()
	s.initExternalData()
	s.initHTTPServer()

	s.runInBackground("HTTP Server", true, func() {
		panic(s.httpServer.ListenAndServe())
	})

	s.runInBackground("Policy Enforcer", true, func() {
		panic(s.Enforce())
	})

	s.wait()
}

func (s *Server) initExternalData() {
	s.externalData = external.NewData(
		users.NewUserLoaderFromLDAP(s.cfg.LDAP),
		secrets.NewSecretLoaderFromDir(s.cfg.SecretsDir),
	)
}

func (s *Server) initStore() {
	b := bolt.NewBoltStore(s.catalog, s.codec)
	err := b.Open(s.cfg.DB.Connection)
	if err != nil {
		panic(fmt.Sprintf("Can't open object store: %s", err))
	}
	s.store = store.New(b)
}

func (s *Server) initHTTPServer() {
	router := httprouter.New()

	version.Serve(router)
	api.ServePolicy(router, s.store, s.codec)
	api.ServeEndpoints(router, s.store)
	api.ServeAdminStore(router, s.store)
	webui.Serve(router)

	var handler http.Handler = router

	handler = handlers.CombinedLoggingHandler(os.Stdout, handler) // todo(slukjanov): make it at least somehow configurable - for example, select file to write to with rotation
	handler = handlers.RecoveryHandler(handlers.PrintRecoveryStack(true))(handler)
	// todo(slukjanov): add configurable handlers.ProxyHeaders to f behind the nginx or any other proxy
	// todo(slukjanov): add compression handler and compress by default in client

	s.httpServer = &http.Server{
		Handler:      handler,
		Addr:         s.cfg.API.ListenAddr(),
		WriteTimeout: 5 * time.Second,
		ReadTimeout:  30 * time.Second,
	}
}
<commit_msg>Make enforcer optional and consume new API<commit_after>package server

import (
	"fmt"
	"github.com/Aptomi/aptomi/pkg/config"
	"github.com/Aptomi/aptomi/pkg/external"
	"github.com/Aptomi/aptomi/pkg/external/secrets"
	"github.com/Aptomi/aptomi/pkg/external/users"
	"github.com/Aptomi/aptomi/pkg/lang"
	"github.com/Aptomi/aptomi/pkg/object"
	"github.com/Aptomi/aptomi/pkg/object/codec"
	"github.com/Aptomi/aptomi/pkg/object/codec/yaml"
	"github.com/Aptomi/aptomi/pkg/object/store/bolt"
	"github.com/Aptomi/aptomi/pkg/server/api"
	"github.com/Aptomi/aptomi/pkg/server/store"
	"github.com/Aptomi/aptomi/pkg/webui"
	"github.com/gorilla/handlers"
	"github.com/julienschmidt/httprouter"
	"net/http"
	"os"
	"time"
)

// Server is a HTTP server which serves API and UI
type Server struct {
	cfg              *config.Server
	backgroundErrors chan string
	catalog          *object.Catalog
	codec            codec.MarshallerUnmarshaller

	externalData *external.Data
	store        store.ServerStore
	httpServer   *http.Server
}

// NewServer creates a new HTTP Server
func NewServer(cfg *config.Server) *Server {
	s := &Server{
		cfg:              cfg,
		backgroundErrors: make(chan string),
	}

	s.catalog = object.NewCatalog().Append(lang.Objects...).Append(store.Objects...)
	s.codec = yaml.NewCodec(s.catalog)

	return s
}

// Start makes HTTP server start serving content
func (s *Server) Start() {
	s.initStore()
	s.initExternalData()
	s.initHTTPServer()

	s.runInBackground("HTTP Server", true, func() {
		panic(s.httpServer.ListenAndServe())
	})

	if !s.cfg.Enforcer.Disabled {
		s.runInBackground("Policy Enforcer", true, func() {
			panic(s.Enforce())
		})
	}

	s.wait()
}

func (s *Server) initExternalData() {
	s.externalData = external.NewData(
		users.NewUserLoaderFromLDAP(s.cfg.LDAP),
		secrets.NewSecretLoaderFromDir(s.cfg.SecretsDir),
	)
}

func (s *Server) initStore() {
	b := bolt.NewBoltStore(s.catalog, s.codec)
	err := b.Open(s.cfg.DB.Connection)
	if err != nil {
		panic(fmt.Sprintf("Can't open object store: %s", err))
	}
	s.store = store.New(b)
}

func (s *Server) initHTTPServer() {
	router := httprouter.New()

	api.New(router, s.store, s.externalData)
	webui.Serve(router)

	var handler http.Handler = router

	// todo write to logrus
	handler = handlers.CombinedLoggingHandler(os.Stdout, handler) // todo(slukjanov): make it at least somehow configurable - for example, select file to write to with rotation
	handler = api.NewPanicHandler(handler)
	// todo(slukjanov): add configurable handlers.ProxyHeaders to f behind the nginx or any other proxy
	// todo(slukjanov): add compression handler and compress by default in client

	s.httpServer = &http.Server{
		Handler:      handler,
		Addr:         s.cfg.API.ListenAddr(),
		WriteTimeout: 5 * time.Second,
		ReadTimeout:  30 * time.Second,
	}
}
<|endoftext|>