<commit_before>// Copyright ©2014 The gonum Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package graph

// All a node needs to do is identify itself. This allows the user to pass in nodes more
// interesting than an int, but also allow us to reap the benefits of having a map-storable,
// ==able type.
type Node interface {
	ID() int
}

// Allows edges to do something more interesting that just be a group of nodes. While the methods
// are called Head and Tail, they are not considered directed unless the given interface specifies
// otherwise.
type Edge interface {
	Head() Node
	Tail() Node
}

// A Graph implements the behavior of an undirected graph.
//
// All methods in Graph are implicitly undirected. Graph algorithms that care about directionality
// will intelligently choose the DirectedGraph behavior if that interface is also implemented,
// even if the function itself only takes in a Graph (or a super-interface of graph).
type Graph interface {
	// NodeExists returns true when node is currently in the graph.
	NodeExists(Node) bool

	// NodeList returns a list of all nodes in no particular order, useful for
	// determining things like if a graph is fully connected. The caller is
	// free to modify this list. Implementations should construct a new list
	// and not return internal representation.
	NodeList() []Node

	// Neighbors returns all nodes connected by any edge to this node.
	Neighbors(Node) []Node

	// EdgeBetween returns an edge between node and neighbor such that
	// Head is one argument and Tail is the other. If no
	// such edge exists, this function returns nil.
	EdgeBetween(node, neighbor Node) Edge
}

// Directed graphs are characterized by having seperable Heads and Tails in their edges.
// That is, if node1 goes to node2, that does not necessarily imply that node2 goes to node1.
//
// While it's possible for a directed graph to have fully reciprocal edges (i.e. the graph is
// symmetric) -- it is not required to be. The graph is also required to implement Graph
// because in many cases it can be useful to know all neighbors regardless of direction.
type DirectedGraph interface {
	Graph
	// Successors gives the nodes connected by OUTBOUND edges.
	// If the graph is an undirected graph, this set is equal to Predecessors.
	Successors(Node) []Node

	// EdgeTo returns an edge between node and successor such that
	// Head returns node and Tail returns successor, if no
	// such edge exists, this function returns nil.
	EdgeTo(node, successor Node) Edge

	// Predecessors gives the nodes connected by INBOUND edges.
	// If the graph is an undirected graph, this set is equal to Successors.
	Predecessors(Node) []Node
}

// Returns all undirected edges in the graph
type EdgeLister interface {
	EdgeList() []Edge
}

type EdgeListGraph interface {
	Graph
	EdgeLister
}

// Returns all directed edges in the graph.
type DirectedEdgeLister interface {
	DirectedEdgeList() []Edge
}

type DirectedEdgeListGraph interface {
	Graph
	DirectedEdgeLister
}

// A crunch graph forces a sparse graph to become a dense graph. That is, if the node IDs are
// [1,4,9,7] it would "crunch" the ids into the contiguous block [0,1,2,3]. Order is not
// required to be preserved between the non-cruched and crunched instances (that means in
// the example above 0 may correspond to 4 or 7 or 9, not necessarily 1).
//
// All dense graphs must have the first ID as 0.
type CrunchGraph interface {
	Graph
	Crunch()
}

// A Graph that implements Coster has an actual cost between adjacent nodes, also known as a
// weighted graph. If a graph implements coster and a function needs to read cost (e.g. A*),
// this function will take precedence over the Uniform Cost function (all weights are 1) if "nil"
// is passed in for the function argument.
//
// If the argument is nil, or the edge is invalid for some reason, this should return math.Inf(1)
type Coster interface {
	Cost(edge Edge) float64
}

type CostGraph interface {
	Coster
	Graph
}

type CostDirectedGraph interface {
	Coster
	DirectedGraph
}

// A graph that implements HeuristicCoster implements a heuristic between any two given nodes.
// Like Coster, if a graph implements this and a function needs a heuristic cost (e.g. A*), this
// function will take precedence over the Null Heuristic (always returns 0) if "nil" is passed in
// for the function argument. If HeuristicCost is not intended to be used, it can be implemented as
// the null heuristic (always returns 0.)
type HeuristicCoster interface {
	// HeuristicCost returns a heuristic cost between any two nodes.
	HeuristicCost(node1, node2 Node) float64
}

// A Mutable is a graph that can have arbitrary nodes and edges added or removed.
//
// Anything implementing Mutable is required to store the actual argument. So if AddNode(myNode) is
// called and later a user calls on the graph graph.NodeList(), the node added by AddNode must be
// an the exact node, not a new node with the same ID.
//
// In any case where conflict is possible (e.g. adding two nodes with the same ID), the later
// call always supercedes the earlier one.
//
// Functions will generally expect one of MutableGraph or MutableDirectedGraph and not Mutable
// itself. That said, any function that takes Mutable[x], the destination mutable should
// always be a different graph than the source.
type Mutable interface {
	// NewNode adds a node with an arbitrary ID and returns the new, unique ID
	// used.
	NewNode() Node

	// Adds a node to the graph. If this is called multiple times for the same ID, the newer node
	// overwrites the old one.
	AddNode(Node)

	// EmptyGraph clears the graph of all nodes and edges.
	EmptyGraph()

	// RemoveNode removes a node from the graph, as well as any edges
	// attached to it. If no such node exists, this is a no-op, not an error.
	RemoveNode(Node)
}

// MutableGraph is an interface ensuring the implementation of the ability to construct
// an arbitrary undirected graph. It is very important to note that any implementation
// of MutableGraph absolutely cannot safely implement the DirectedGraph interface.
//
// A MutableGraph is required to store any Edge argument in the same way Mutable must
// store a Node argument -- any retrieval call is required to return the exact supplied edge.
// This is what makes it incompatible with DirectedGraph.
//
// A call to AddEdgeBetween(Edge{head,tail}) make is so there is simply no way to safely
// return EdgeTo(tail, head) since the edge returned will, by this contract, need to be
// Head() == head and Tail() == tail when the reverse must be true to fulfill the
// functionality guaranteed of EdgeTo.
type MutableGraph interface {
	CostGraph
	Mutable

	// Like EdgeBetween in Graph, AddEdgeBetween adds an edge between two nodes.
	// If one or both nodes do not exist, the Graph is expected to add them.
	AddEdgeBetween(e Edge, cost float64)

	// RemoveEdge clears the stored edge between two nodes. Calling this will never
	// remove a node. If the edge does not exist this is a no-op, not an error.
	RemoveEdgeBetween(e Edge)
}

// MutableDirectedGraph is an interface that ensures one can construct an arbitrary directed
// graph. Naturally, a MutableDirectedGraph works for both undirected and directed cases,
// but simply using a MutableGraph may be cleaner. As the documentation for MutableGraph
// notes, however, a graph cannot safely implement MutableGraph and MutableDirectedGraph
// at the same time, because of the functionality of a EdgeTo in DirectedGraph.
type MutableDirectedGraph interface {
	CostDirectedGraph
	Mutable

	// Adds an edge FROM e.Head TO e.Tail. Newer calls overwrite older ones.
	// If the nodes Head or Tail do not exist in the graph, this must add them.
	AddEdgeTo(e Edge, cost float64)

	// Removes an edge FROM e.Head TO e.Tail. If no such edge exists, this is a no-op,
	// not an error.
	RemoveEdgeTo(e Edge)
}

// A function that returns the cost of following an edge
type CostFunc func(Edge) float64

// Estimates the cost of travelling between two nodes
type HeuristicCostFunc func(Node, Node) float64
<commit_msg>Removed EmptyGraph<commit_after>// Copyright ©2014 The gonum Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package graph

// All a node needs to do is identify itself. This allows the user to pass in nodes more
// interesting than an int, but also allow us to reap the benefits of having a map-storable,
// ==able type.
type Node interface {
	ID() int
}

// Allows edges to do something more interesting that just be a group of nodes. While the methods
// are called Head and Tail, they are not considered directed unless the given interface specifies
// otherwise.
type Edge interface {
	Head() Node
	Tail() Node
}

// A Graph implements the behavior of an undirected graph.
//
// All methods in Graph are implicitly undirected. Graph algorithms that care about directionality
// will intelligently choose the DirectedGraph behavior if that interface is also implemented,
// even if the function itself only takes in a Graph (or a super-interface of graph).
type Graph interface {
	// NodeExists returns true when node is currently in the graph.
	NodeExists(Node) bool

	// NodeList returns a list of all nodes in no particular order, useful for
	// determining things like if a graph is fully connected. The caller is
	// free to modify this list. Implementations should construct a new list
	// and not return internal representation.
	NodeList() []Node

	// Neighbors returns all nodes connected by any edge to this node.
	Neighbors(Node) []Node

	// EdgeBetween returns an edge between node and neighbor such that
	// Head is one argument and Tail is the other. If no
	// such edge exists, this function returns nil.
	EdgeBetween(node, neighbor Node) Edge
}

// Directed graphs are characterized by having seperable Heads and Tails in their edges.
// That is, if node1 goes to node2, that does not necessarily imply that node2 goes to node1.
//
// While it's possible for a directed graph to have fully reciprocal edges (i.e. the graph is
// symmetric) -- it is not required to be. The graph is also required to implement Graph
// because in many cases it can be useful to know all neighbors regardless of direction.
type DirectedGraph interface {
	Graph
	// Successors gives the nodes connected by OUTBOUND edges.
	// If the graph is an undirected graph, this set is equal to Predecessors.
	Successors(Node) []Node

	// EdgeTo returns an edge between node and successor such that
	// Head returns node and Tail returns successor, if no
	// such edge exists, this function returns nil.
	EdgeTo(node, successor Node) Edge

	// Predecessors gives the nodes connected by INBOUND edges.
	// If the graph is an undirected graph, this set is equal to Successors.
	Predecessors(Node) []Node
}

// Returns all undirected edges in the graph
type EdgeLister interface {
	EdgeList() []Edge
}

type EdgeListGraph interface {
	Graph
	EdgeLister
}

// Returns all directed edges in the graph.
type DirectedEdgeLister interface {
	DirectedEdgeList() []Edge
}

type DirectedEdgeListGraph interface {
	Graph
	DirectedEdgeLister
}

// A crunch graph forces a sparse graph to become a dense graph. That is, if the node IDs are
// [1,4,9,7] it would "crunch" the ids into the contiguous block [0,1,2,3]. Order is not
// required to be preserved between the non-cruched and crunched instances (that means in
// the example above 0 may correspond to 4 or 7 or 9, not necessarily 1).
//
// All dense graphs must have the first ID as 0.
type CrunchGraph interface {
	Graph
	Crunch()
}

// A Graph that implements Coster has an actual cost between adjacent nodes, also known as a
// weighted graph. If a graph implements coster and a function needs to read cost (e.g. A*),
// this function will take precedence over the Uniform Cost function (all weights are 1) if "nil"
// is passed in for the function argument.
//
// If the argument is nil, or the edge is invalid for some reason, this should return math.Inf(1)
type Coster interface {
	Cost(edge Edge) float64
}

type CostGraph interface {
	Coster
	Graph
}

type CostDirectedGraph interface {
	Coster
	DirectedGraph
}

// A graph that implements HeuristicCoster implements a heuristic between any two given nodes.
// Like Coster, if a graph implements this and a function needs a heuristic cost (e.g. A*), this
// function will take precedence over the Null Heuristic (always returns 0) if "nil" is passed in
// for the function argument. If HeuristicCost is not intended to be used, it can be implemented as
// the null heuristic (always returns 0.)
type HeuristicCoster interface {
	// HeuristicCost returns a heuristic cost between any two nodes.
	HeuristicCost(node1, node2 Node) float64
}

// A Mutable is a graph that can have arbitrary nodes and edges added or removed.
//
// Anything implementing Mutable is required to store the actual argument. So if AddNode(myNode) is
// called and later a user calls on the graph graph.NodeList(), the node added by AddNode must be
// an the exact node, not a new node with the same ID.
//
// In any case where conflict is possible (e.g. adding two nodes with the same ID), the later
// call always supercedes the earlier one.
//
// Functions will generally expect one of MutableGraph or MutableDirectedGraph and not Mutable
// itself. That said, any function that takes Mutable[x], the destination mutable should
// always be a different graph than the source.
type Mutable interface {
	// NewNode adds a node with an arbitrary ID and returns the new, unique ID
	// used.
	NewNode() Node

	// Adds a node to the graph. If this is called multiple times for the same ID, the newer node
	// overwrites the old one.
	AddNode(Node)

	// RemoveNode removes a node from the graph, as well as any edges
	// attached to it. If no such node exists, this is a no-op, not an error.
	RemoveNode(Node)
}

// MutableGraph is an interface ensuring the implementation of the ability to construct
// an arbitrary undirected graph. It is very important to note that any implementation
// of MutableGraph absolutely cannot safely implement the DirectedGraph interface.
//
// A MutableGraph is required to store any Edge argument in the same way Mutable must
// store a Node argument -- any retrieval call is required to return the exact supplied edge.
// This is what makes it incompatible with DirectedGraph.
//
// A call to AddEdgeBetween(Edge{head,tail}) make is so there is simply no way to safely
// return EdgeTo(tail, head) since the edge returned will, by this contract, need to be
// Head() == head and Tail() == tail when the reverse must be true to fulfill the
// functionality guaranteed of EdgeTo.
type MutableGraph interface {
	CostGraph
	Mutable

	// Like EdgeBetween in Graph, AddEdgeBetween adds an edge between two nodes.
	// If one or both nodes do not exist, the Graph is expected to add them.
	AddEdgeBetween(e Edge, cost float64)

	// RemoveEdge clears the stored edge between two nodes. Calling this will never
	// remove a node. If the edge does not exist this is a no-op, not an error.
	RemoveEdgeBetween(e Edge)
}

// MutableDirectedGraph is an interface that ensures one can construct an arbitrary directed
// graph. Naturally, a MutableDirectedGraph works for both undirected and directed cases,
// but simply using a MutableGraph may be cleaner. As the documentation for MutableGraph
// notes, however, a graph cannot safely implement MutableGraph and MutableDirectedGraph
// at the same time, because of the functionality of a EdgeTo in DirectedGraph.
type MutableDirectedGraph interface {
	CostDirectedGraph
	Mutable

	// Adds an edge FROM e.Head TO e.Tail. Newer calls overwrite older ones.
	// If the nodes Head or Tail do not exist in the graph, this must add them.
	AddEdgeTo(e Edge, cost float64)

	// Removes an edge FROM e.Head TO e.Tail. If no such edge exists, this is a no-op,
	// not an error.
	RemoveEdgeTo(e Edge)
}

// A function that returns the cost of following an edge
type CostFunc func(Edge) float64

// Estimates the cost of travelling between two nodes
type HeuristicCostFunc func(Node, Node) float64
<|endoftext|>


<commit_before>package main

import (
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"os/exec"
	"strings"
	"time"
)

func main() {
	s := &http.Server{
		Addr:           ":8080",
		ReadTimeout:    5 * time.Second,
		WriteTimeout:   5 * time.Second,
		MaxHeaderBytes: 1 << 20,
	}

	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		if r.Method == "POST" {
			process := os.Getenv("fprocess")
			parts := strings.Split(process, " ")
			targetCmd := exec.Command(parts[0], parts[1:]...)
			writer, _ := targetCmd.StdinPipe()
			res, _ := ioutil.ReadAll(r.Body)
			writer.Write(res)
			writer.Close()
			out, err := targetCmd.Output()
			if err != nil {
				panic(err)
			}

			os.Stdout.Write(out)
			w.Write(out)
		}
	})

	log.Fatal(s.ListenAndServe())
}
<commit_msg>fprocess error handling and viewing<commit_after>package main

import (
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"os/exec"
	"strings"
	"time"
)

func main() {
	s := &http.Server{
		Addr:           ":8080",
		ReadTimeout:    5 * time.Second,
		WriteTimeout:   5 * time.Second,
		MaxHeaderBytes: 1 << 20,
	}

	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		if r.Method == "POST" {
			// get the fprocess to execute and it's args
			process := os.Getenv("fprocess")
			//

			// split the fprocess and it's args
			parts := strings.Split(process, " ")
			//

			// prepare the Command object for the fprocess
			fprocessCmd := exec.Command(parts[0], parts[1:]...)
			//

			// get a reference on all outputs for the fprocess (as bytes.Buffer)
			var stdout bytes.Buffer
			fprocessCmd.Stdout = &stdout

			var stderr bytes.Buffer
			fprocessCmd.Stderr = &stderr
			//

			// write the http.Request's POST body to the stdin of the fprocess
			stdin, _ := fprocessCmd.StdinPipe()
			res, _ := ioutil.ReadAll(r.Body)
			stdin.Write(res)
			stdin.Close()
			//

			// execute the fprocess
			err := fprocessCmd.Run()
			//

			if err != nil {
				// get the fprocess's stderr content
				errorStack := stderr.String()
				//

				// print the error's details to the logger
				log.Println(err.Error())
				log.Println(errorStack)
				//

				// send the http response with the error that occured
				w.WriteHeader(500)
				response := bytes.NewBufferString(err.Error() + "\n" + errorStack)
				w.Write(response.Bytes())
				//

				return
			}

			// TODO: consider stdout to container as configurable via env-variable.
			// write the fprocess's stdout to the fwatchdog stdout
			os.Stdout.Write(stdout.Bytes())
			//

			// send the http response with the fprocess's stdout
			w.WriteHeader(200)
			w.Write(stdout.Bytes())
			//
		}
	})

	log.Fatal(s.ListenAndServe())
}
<|endoftext|>


<commit_before>package admin

import (
	ccom "github.com/ceph/go-ceph/common/commands"
	"github.com/ceph/go-ceph/internal/commands"
)

// SnapshotMirrorAdmin helps administer the snapshot mirroring features of
// cephfs. Snapshot mirroring is only available in ceph pacific and later.
type SnapshotMirrorAdmin struct {
	conn ccom.MgrCommander
}

// SnapshotMirror returns a new SnapshotMirrorAdmin to be used for the
// administration of snapshot mirroring features.
func (fsa *FSAdmin) SnapshotMirror() *SnapshotMirrorAdmin {
	return &SnapshotMirrorAdmin{conn: fsa.conn}
}

// Enable snapshot mirroring for the given file system.
//
// Similar To:
//  ceph fs snapshot mirror enable <fs_name>
func (sma *SnapshotMirrorAdmin) Enable(fsname string) error {
	m := map[string]string{
		"prefix":  "fs snapshot mirror enable",
		"fs_name": fsname,
		"format":  "json",
	}
	return commands.MarshalMgrCommand(sma.conn, m).NoStatus().EmptyBody().End()
}

// Disable snapshot mirroring for the given file system.
//
// Similar To:
//  ceph fs snapshot mirror disable <fs_name>
func (sma *SnapshotMirrorAdmin) Disable(fsname string) error {
	m := map[string]string{
		"prefix":  "fs snapshot mirror disable",
		"fs_name": fsname,
		"format":  "json",
	}
	return commands.MarshalMgrCommand(sma.conn, m).NoStatus().EmptyBody().End()
}

// Add a path in the file system to be mirrored.
//
// Similar To:
//  ceph fs snapshot mirror add <fs_name> <path>
func (sma *SnapshotMirrorAdmin) Add(fsname, path string) error {
	m := map[string]string{
		"prefix":  "fs snapshot mirror add",
		"fs_name": fsname,
		"path":    path,
		"format":  "json",
	}
	return commands.MarshalMgrCommand(sma.conn, m).NoStatus().EmptyBody().End()
}

// Remove a path in the file system from mirroring.
//
// Similar To:
//  ceph fs snapshot mirror remove <fs_name> <path>
func (sma *SnapshotMirrorAdmin) Remove(fsname, path string) error {
	m := map[string]string{
		"prefix":  "fs snapshot mirror remove",
		"fs_name": fsname,
		"path":    path,
		"format":  "json",
	}
	return commands.MarshalMgrCommand(sma.conn, m).NoStatus().EmptyBody().End()
}

type bootstrapTokenResponse struct {
	Token string `json:"token"`
}

// CreatePeerBootstrapToken returns a token that can be used to create
// a peering association between this site an another site.
//
// Similar To:
//  ceph fs snapshot mirror peer_bootstrap create <fs_name> <client_entity> <site-name>
func (sma *SnapshotMirrorAdmin) CreatePeerBootstrapToken(
	fsname, client, site string) (string, error) {
	m := map[string]string{
		"prefix":      "fs snapshot mirror peer_bootstrap create",
		"fs_name":     fsname,
		"client_name": client,
		"format":      "json",
	}
	if site != "" {
		m["site_name"] = site
	}
	var bt bootstrapTokenResponse
	err := commands.MarshalMgrCommand(sma.conn, m).NoStatus().Unmarshal(&bt).End()
	return bt.Token, err
}

// ImportPeerBoostrapToken creates an association between another site, one
// that has provided a token, with the current site.
//
// Similar To:
//  ceph fs snapshot mirror peer_bootstrap import <fs_name> <token>
func (sma *SnapshotMirrorAdmin) ImportPeerBoostrapToken(fsname, token string) error {
	m := map[string]string{
		"prefix":  "fs snapshot mirror peer_bootstrap import",
		"fs_name": fsname,
		"token":   token,
		"format":  "json",
	}
	return commands.MarshalMgrCommand(sma.conn, m).NoStatus().EmptyBody().End()
}

// DaemonID represents the ID of a cephfs mirroring daemon.
type DaemonID uint

// FileSystemID represents the ID of a cephfs file system.
type FileSystemID uint

// PeerUUID represents the UUID of a cephfs mirroring peer.
type PeerUUID string

// DaemonStatusPeerRemote contains fields detailing a remote peer.
type DaemonStatusPeerRemote struct {
	ClientName  string `json:"client_name"`
	ClusterName string `json:"cluster_name"`
	FSName      string `json:"fs_name"`
}

// DaemonStatusPeerStats contains fields detailing the a remote peer's stats.
type DaemonStatusPeerStats struct {
	FailureCount  uint64 `json:"failure_count"`
	RecoveryCount uint64 `json:"recovery_count"`
}

// DaemonStatusPeerInfo contains fields representing information about a remote peer.
type DaemonStatusPeerInfo struct {
	UUID   PeerUUID               `json:"uuid"`
	Remote DaemonStatusPeerRemote `json:"remote"`
	Stats  DaemonStatusPeerStats  `json:"stats"`
}

// DaemonStatusFileSystemInfo represents information about a mirrored file system.
type DaemonStatusFileSystemInfo struct {
	FileSystemID   FileSystemID           `json:"filesystem_id"`
	Name           string                 `json:"name"`
	DirectoryCount int64                  `json:"directory_count"`
	Peers          []DaemonStatusPeerInfo `json:"peers"`
}

// DaemonStatusInfo maps file system IDs to information about that file system.
type DaemonStatusInfo struct {
	DaemonID    DaemonID                     `json:"daemon_id"`
	FileSystems []DaemonStatusFileSystemInfo `json:"filesystems"`
}

// DaemonStatusResults maps mirroring daemon IDs to information about that
// mirroring daemon.
type DaemonStatusResults []DaemonStatusInfo

func parseDaemonStatus(res response) (DaemonStatusResults, error) {
	var dsr DaemonStatusResults
	if err := res.NoStatus().Unmarshal(&dsr).End(); err != nil {
		return nil, err
	}
	return dsr, nil
}

// DaemonStatus returns information on the status of cephfs mirroring daemons
// associated with the given file system.
//
// Similar To:
//  ceph fs snapshot mirror daemon status <fs_name>
func (sma *SnapshotMirrorAdmin) DaemonStatus(fsname string) (
	DaemonStatusResults, error) {
	// ---
	m := map[string]string{
		"prefix":  "fs snapshot mirror daemon status",
		"fs_name": fsname,
		"format":  "json",
	}
	return parseDaemonStatus(commands.MarshalMgrCommand(sma.conn, m))
}

// PeerInfo includes information about a cephfs mirroring peer.
type PeerInfo struct {
	ClientName string `json:"client_name"`
	SiteName   string `json:"site_name"`
	FSName     string `json:"fs_name"`
	MonHost    string `json:"mon_host"`
}

// PeerListResults maps a peer's UUID to information about that peer.
type PeerListResults map[PeerUUID]PeerInfo

func parsePeerList(res response) (PeerListResults, error) {
	var plr PeerListResults
	if err := res.NoStatus().Unmarshal(&plr).End(); err != nil {
		return nil, err
	}
	return plr, nil
}

// PeerList returns information about peers associated with the given file system.
//
// Similar To:
//  ceph fs snapshot mirror peer_list <fs_name>
func (sma *SnapshotMirrorAdmin) PeerList(fsname string) (
	PeerListResults, error) {
	// ---
	m := map[string]string{
		"prefix":  "fs snapshot mirror peer_list",
		"fs_name": fsname,
		"format":  "json",
	}
	return parsePeerList(commands.MarshalMgrCommand(sma.conn, m))
}

/*
 DirMap - figure out what last_shuffled is supposed to mean and, if it is a time
          like it seems to be, how best to represent in Go.

 DirMap TODO
ceph fs snapshot mirror dirmap
func (sma *SnapshotMirrorAdmin) DirMap(fsname, path string) error {
	m := map[string]string{
		"prefix":  "fs snapshot mirror dirmap",
		"fs_name": fsname,
		"path":    path,
		"format":  "json",
	}
	return commands.MarshalMgrCommand(sma.conn, m).NoStatus().EmptyBody().End()
}
*/
<commit_msg>cephfs admin: tweak naming of DaemonStatusPeer<commit_after>package admin

import (
	ccom "github.com/ceph/go-ceph/common/commands"
	"github.com/ceph/go-ceph/internal/commands"
)

// SnapshotMirrorAdmin helps administer the snapshot mirroring features of
// cephfs. Snapshot mirroring is only available in ceph pacific and later.
type SnapshotMirrorAdmin struct {
	conn ccom.MgrCommander
}

// SnapshotMirror returns a new SnapshotMirrorAdmin to be used for the
// administration of snapshot mirroring features.
func (fsa *FSAdmin) SnapshotMirror() *SnapshotMirrorAdmin {
	return &SnapshotMirrorAdmin{conn: fsa.conn}
}

// Enable snapshot mirroring for the given file system.
//
// Similar To:
//  ceph fs snapshot mirror enable <fs_name>
func (sma *SnapshotMirrorAdmin) Enable(fsname string) error {
	m := map[string]string{
		"prefix":  "fs snapshot mirror enable",
		"fs_name": fsname,
		"format":  "json",
	}
	return commands.MarshalMgrCommand(sma.conn, m).NoStatus().EmptyBody().End()
}

// Disable snapshot mirroring for the given file system.
//
// Similar To:
//  ceph fs snapshot mirror disable <fs_name>
func (sma *SnapshotMirrorAdmin) Disable(fsname string) error {
	m := map[string]string{
		"prefix":  "fs snapshot mirror disable",
		"fs_name": fsname,
		"format":  "json",
	}
	return commands.MarshalMgrCommand(sma.conn, m).NoStatus().EmptyBody().End()
}

// Add a path in the file system to be mirrored.
//
// Similar To:
//  ceph fs snapshot mirror add <fs_name> <path>
func (sma *SnapshotMirrorAdmin) Add(fsname, path string) error {
	m := map[string]string{
		"prefix":  "fs snapshot mirror add",
		"fs_name": fsname,
		"path":    path,
		"format":  "json",
	}
	return commands.MarshalMgrCommand(sma.conn, m).NoStatus().EmptyBody().End()
}

// Remove a path in the file system from mirroring.
//
// Similar To:
//  ceph fs snapshot mirror remove <fs_name> <path>
func (sma *SnapshotMirrorAdmin) Remove(fsname, path string) error {
	m := map[string]string{
		"prefix":  "fs snapshot mirror remove",
		"fs_name": fsname,
		"path":    path,
		"format":  "json",
	}
	return commands.MarshalMgrCommand(sma.conn, m).NoStatus().EmptyBody().End()
}

type bootstrapTokenResponse struct {
	Token string `json:"token"`
}

// CreatePeerBootstrapToken returns a token that can be used to create
// a peering association between this site an another site.
//
// Similar To:
//  ceph fs snapshot mirror peer_bootstrap create <fs_name> <client_entity> <site-name>
func (sma *SnapshotMirrorAdmin) CreatePeerBootstrapToken(
	fsname, client, site string) (string, error) {
	m := map[string]string{
		"prefix":      "fs snapshot mirror peer_bootstrap create",
		"fs_name":     fsname,
		"client_name": client,
		"format":      "json",
	}
	if site != "" {
		m["site_name"] = site
	}
	var bt bootstrapTokenResponse
	err := commands.MarshalMgrCommand(sma.conn, m).NoStatus().Unmarshal(&bt).End()
	return bt.Token, err
}

// ImportPeerBoostrapToken creates an association between another site, one
// that has provided a token, with the current site.
//
// Similar To:
//  ceph fs snapshot mirror peer_bootstrap import <fs_name> <token>
func (sma *SnapshotMirrorAdmin) ImportPeerBoostrapToken(fsname, token string) error {
	m := map[string]string{
		"prefix":  "fs snapshot mirror peer_bootstrap import",
		"fs_name": fsname,
		"token":   token,
		"format":  "json",
	}
	return commands.MarshalMgrCommand(sma.conn, m).NoStatus().EmptyBody().End()
}

// DaemonID represents the ID of a cephfs mirroring daemon.
type DaemonID uint

// FileSystemID represents the ID of a cephfs file system.
type FileSystemID uint

// PeerUUID represents the UUID of a cephfs mirroring peer.
type PeerUUID string

// DaemonStatusPeer contains fields detailing a remote peer.
type DaemonStatusPeer struct {
	ClientName  string `json:"client_name"`
	ClusterName string `json:"cluster_name"`
	FSName      string `json:"fs_name"`
}

// DaemonStatusPeerStats contains fields detailing the a remote peer's stats.
type DaemonStatusPeerStats struct {
	FailureCount  uint64 `json:"failure_count"`
	RecoveryCount uint64 `json:"recovery_count"`
}

// DaemonStatusPeerInfo contains fields representing information about a remote peer.
type DaemonStatusPeerInfo struct {
	UUID   PeerUUID              `json:"uuid"`
	Remote DaemonStatusPeer      `json:"remote"`
	Stats  DaemonStatusPeerStats `json:"stats"`
}

// DaemonStatusFileSystemInfo represents information about a mirrored file system.
type DaemonStatusFileSystemInfo struct {
	FileSystemID   FileSystemID           `json:"filesystem_id"`
	Name           string                 `json:"name"`
	DirectoryCount int64                  `json:"directory_count"`
	Peers          []DaemonStatusPeerInfo `json:"peers"`
}

// DaemonStatusInfo maps file system IDs to information about that file system.
type DaemonStatusInfo struct {
	DaemonID    DaemonID                     `json:"daemon_id"`
	FileSystems []DaemonStatusFileSystemInfo `json:"filesystems"`
}

// DaemonStatusResults maps mirroring daemon IDs to information about that
// mirroring daemon.
type DaemonStatusResults []DaemonStatusInfo

func parseDaemonStatus(res response) (DaemonStatusResults, error) {
	var dsr DaemonStatusResults
	if err := res.NoStatus().Unmarshal(&dsr).End(); err != nil {
		return nil, err
	}
	return dsr, nil
}

// DaemonStatus returns information on the status of cephfs mirroring daemons
// associated with the given file system.
//
// Similar To:
//  ceph fs snapshot mirror daemon status <fs_name>
func (sma *SnapshotMirrorAdmin) DaemonStatus(fsname string) (
	DaemonStatusResults, error) {
	// ---
	m := map[string]string{
		"prefix":  "fs snapshot mirror daemon status",
		"fs_name": fsname,
		"format":  "json",
	}
	return parseDaemonStatus(commands.MarshalMgrCommand(sma.conn, m))
}

// PeerInfo includes information about a cephfs mirroring peer.
type PeerInfo struct {
	ClientName string `json:"client_name"`
	SiteName   string `json:"site_name"`
	FSName     string `json:"fs_name"`
	MonHost    string `json:"mon_host"`
}

// PeerListResults maps a peer's UUID to information about that peer.
type PeerListResults map[PeerUUID]PeerInfo

func parsePeerList(res response) (PeerListResults, error) {
	var plr PeerListResults
	if err := res.NoStatus().Unmarshal(&plr).End(); err != nil {
		return nil, err
	}
	return plr, nil
}

// PeerList returns information about peers associated with the given file system.
//
// Similar To:
//  ceph fs snapshot mirror peer_list <fs_name>
func (sma *SnapshotMirrorAdmin) PeerList(fsname string) (
	PeerListResults, error) {
	// ---
	m := map[string]string{
		"prefix":  "fs snapshot mirror peer_list",
		"fs_name": fsname,
		"format":  "json",
	}
	return parsePeerList(commands.MarshalMgrCommand(sma.conn, m))
}

/*
 DirMap - figure out what last_shuffled is supposed to mean and, if it is a time
          like it seems to be, how best to represent in Go.

 DirMap TODO
ceph fs snapshot mirror dirmap
func (sma *SnapshotMirrorAdmin) DirMap(fsname, path string) error {
	m := map[string]string{
		"prefix":  "fs snapshot mirror dirmap",
		"fs_name": fsname,
		"path":    path,
		"format":  "json",
	}
	return commands.MarshalMgrCommand(sma.conn, m).NoStatus().EmptyBody().End()
}
*/
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"strings"
	"time"

	"github.com/allanliu/easylogger"
	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/cloudformation"
	"github.com/aws/aws-sdk-go/service/cloudformation/cloudformationiface"
	"github.com/aws/aws-sdk-go/service/s3"
	"github.com/aws/aws-sdk-go/service/s3/s3iface"
)

var (
	awsRegion = flag.String(
		"aws-region",
		"us-east-1",
		"AWS region",
	)
	bucketFilter = flag.String(
		"bucket-filter",
		"exhibitors3bucket",
		"Search critieria for buckets that fall under CF",
	)
)

type cfS3BucketCleanup struct {
	cfSVC        cloudformationiface.CloudFormationAPI
	s3SVC        s3iface.S3API
	stacks       []*cloudformation.StackSummary
	bucketFilter string
}

func getSessionConfigs() (*session.Session, *aws.Config) {
	return session.New(), &aws.Config{Region: awsRegion}
}

func (c *cfS3BucketCleanup) getAllCfStackNames() {
	params := &cloudformation.ListStacksInput{
		StackStatusFilter: []*string{
			aws.String(cloudformation.StackStatusCreateComplete),
		},
	}
	resp, err := c.cfSVC.ListStacks(params)
	easylogger.LogFatal(err)
	c.stacks = resp.StackSummaries
}

func checkStackBucketbyName(bucketName string, stackName string) bool {
	return strings.Contains(bucketName, stackName)
}

func isCloudformationBucket(bucketName string, bucketFilter string) bool {
	return strings.Contains(bucketName, bucketFilter)
}

func checkStackBucketbyDate(bucketDate time.Time, stackDate time.Time) bool {
	diff := stackDate.Sub(bucketDate).Seconds()
	return diff < 60 && diff > -60
}

func (c *cfS3BucketCleanup) isBucketDeletable(bucket *s3.Bucket) bool {
	for _, stack := range c.stacks {
		if checkStackBucketbyName(*bucket.Name, *stack.StackName) &&
			checkStackBucketbyDate(
				*bucket.CreationDate,
				*stack.CreationTime,
			) {
			return false
		}
	}
	return true
}

func (c *cfS3BucketCleanup) getBucketContents(bucket *s3.Bucket) []*s3.Object {
	resp, err := c.s3SVC.ListObjects(
		&s3.ListObjectsInput{
			Bucket: bucket.Name,
		},
	)
	easylogger.LogFatal(err)
	return resp.Contents
}

func getObjectIDStruct(objects []*s3.Object) []*s3.ObjectIdentifier {
	var result []*s3.ObjectIdentifier
	for _, object := range objects {
		result = append(
			result,
			&s3.ObjectIdentifier{
				Key: object.Key,
			},
		)
	}
	return result
}

func isBucketEmpty(objects []*s3.Object) bool {
	return len(objects) <= 0
}

func (c *cfS3BucketCleanup) emptyBucket(
	bucket *s3.Bucket,
	objects []*s3.Object,
) []*s3.Error {
	resp, err := c.s3SVC.DeleteObjects(
		&s3.DeleteObjectsInput{
			Bucket: bucket.Name,
			Delete: &s3.Delete{
				Objects: getObjectIDStruct(objects),
			},
		},
	)
	easylogger.LogFatal(err)
	if resp.Errors == nil {
		return []*s3.Error{}
	}
	return resp.Errors
}

func (c *cfS3BucketCleanup) removeUnusedCFBuckets() []*s3.Error {
	var (
		errors  = []*s3.Error{}
		objects []*s3.Object
	)
	resp, err := c.s3SVC.ListBuckets(&s3.ListBucketsInput{})
	easylogger.LogFatal(err)
	for _, bucket := range resp.Buckets {
		if isCloudformationBucket(*bucket.Name, c.bucketFilter) &&
			c.isBucketDeletable(bucket) {
			//fmt.Println("This bucket is to be deleted ", *bucket.Name)
			objects = c.getBucketContents(bucket)
			if !isBucketEmpty(objects) {
				errs := c.emptyBucket(bucket, objects)
				if len(errs) > 0 {
					errors = append(errors, errs...)
					continue
				}
			}
			_, err := c.s3SVC.DeleteBucket(
				&s3.DeleteBucketInput{
					Bucket: bucket.Name,
				},
			)
			easylogger.LogFatal(err)

		} //else {
		//	fmt.Println("This bucket is NOT to be deleted", *bucket.Name)
		//}
	}
	return errors
}
<commit_msg>Added a log message and Removed old comment<commit_after>package main

import (
	"flag"
	"strings"
	"time"

	"github.com/allanliu/easylogger"
	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/cloudformation"
	"github.com/aws/aws-sdk-go/service/cloudformation/cloudformationiface"
	"github.com/aws/aws-sdk-go/service/s3"
	"github.com/aws/aws-sdk-go/service/s3/s3iface"
)

var (
	awsRegion = flag.String(
		"aws-region",
		"us-east-1",
		"AWS region",
	)
	bucketFilter = flag.String(
		"bucket-filter",
		"exhibitors3bucket",
		"Search critieria for buckets that fall under CF",
	)
)

type cfS3BucketCleanup struct {
	cfSVC        cloudformationiface.CloudFormationAPI
	s3SVC        s3iface.S3API
	stacks       []*cloudformation.StackSummary
	bucketFilter string
}

func getSessionConfigs() (*session.Session, *aws.Config) {
	return session.New(), &aws.Config{Region: awsRegion}
}

func (c *cfS3BucketCleanup) getAllCfStackNames() {
	params := &cloudformation.ListStacksInput{
		StackStatusFilter: []*string{
			aws.String(cloudformation.StackStatusCreateComplete),
		},
	}
	resp, err := c.cfSVC.ListStacks(params)
	easylogger.LogFatal(err)
	c.stacks = resp.StackSummaries
}

func checkStackBucketbyName(bucketName string, stackName string) bool {
	return strings.Contains(bucketName, stackName)
}

func isCloudformationBucket(bucketName string, bucketFilter string) bool {
	return strings.Contains(bucketName, bucketFilter)
}

func checkStackBucketbyDate(bucketDate time.Time, stackDate time.Time) bool {
	diff := stackDate.Sub(bucketDate).Seconds()
	return diff < 60 && diff > -60
}

func (c *cfS3BucketCleanup) isBucketDeletable(bucket *s3.Bucket) bool {
	for _, stack := range c.stacks {
		if checkStackBucketbyName(*bucket.Name, *stack.StackName) &&
			checkStackBucketbyDate(
				*bucket.CreationDate,
				*stack.CreationTime,
			) {
			return false
		}
	}
	return true
}

func (c *cfS3BucketCleanup) getBucketContents(bucket *s3.Bucket) []*s3.Object {
	resp, err := c.s3SVC.ListObjects(
		&s3.ListObjectsInput{
			Bucket: bucket.Name,
		},
	)
	easylogger.LogFatal(err)
	return resp.Contents
}

func getObjectIDStruct(objects []*s3.Object) []*s3.ObjectIdentifier {
	var result []*s3.ObjectIdentifier
	for _, object := range objects {
		result = append(
			result,
			&s3.ObjectIdentifier{
				Key: object.Key,
			},
		)
	}
	return result
}

func isBucketEmpty(objects []*s3.Object) bool {
	return len(objects) <= 0
}

func (c *cfS3BucketCleanup) emptyBucket(
	bucket *s3.Bucket,
	objects []*s3.Object,
) []*s3.Error {
	resp, err := c.s3SVC.DeleteObjects(
		&s3.DeleteObjectsInput{
			Bucket: bucket.Name,
			Delete: &s3.Delete{
				Objects: getObjectIDStruct(objects),
			},
		},
	)
	easylogger.LogFatal(err)
	if resp.Errors == nil {
		return []*s3.Error{}
	}
	return resp.Errors
}

func (c *cfS3BucketCleanup) removeUnusedCFBuckets() []*s3.Error {
	var (
		errors  = []*s3.Error{}
		objects []*s3.Object
	)
	resp, err := c.s3SVC.ListBuckets(&s3.ListBucketsInput{})
	easylogger.LogFatal(err)
	for _, bucket := range resp.Buckets {
		if isCloudformationBucket(*bucket.Name, c.bucketFilter) &&
			c.isBucketDeletable(bucket) {
			easylogger.Log("This bucket is to be deleted: ", *bucket.Name)
			objects = c.getBucketContents(bucket)
			if !isBucketEmpty(objects) {
				errs := c.emptyBucket(bucket, objects)
				if len(errs) > 0 {
					errors = append(errors, errs...)
					continue
				}
			}
			_, err := c.s3SVC.DeleteBucket(
				&s3.DeleteBucketInput{
					Bucket: bucket.Name,
				},
			)
			easylogger.LogFatal(err)

		}
	}
	return errors
}
<|endoftext|>


<commit_before>package allocrunner

import (
	"sync"
	"time"

	log "github.com/hashicorp/go-hclog"
	"github.com/hashicorp/nomad/client/allocrunner/interfaces"
	"github.com/hashicorp/nomad/client/consul"
	"github.com/hashicorp/nomad/client/taskenv"
	agentconsul "github.com/hashicorp/nomad/command/agent/consul"
	"github.com/hashicorp/nomad/nomad/structs"
)

const (
	groupServiceHookName = "group_services"
)

type networkStatusGetter interface {
	NetworkStatus() *structs.AllocNetworkStatus
}

// groupServiceHook manages task group Consul service registration and
// deregistration.
type groupServiceHook struct {
	allocID             string
	group               string
	restarter           agentconsul.WorkloadRestarter
	consulClient        consul.ConsulServiceAPI
	consulNamespace     string
	prerun              bool
	delay               time.Duration
	deregistered        bool
	networkStatusGetter networkStatusGetter

	logger log.Logger

	// The following fields may be updated
	canary         bool
	services       []*structs.Service
	networks       structs.Networks
	ports          structs.AllocatedPorts
	taskEnvBuilder *taskenv.Builder

	// Since Update() may be called concurrently with any other hook all
	// hook methods must be fully serialized
	mu sync.Mutex
}

type groupServiceHookConfig struct {
	alloc               *structs.Allocation
	consul              consul.ConsulServiceAPI
	consulNamespace     string
	restarter           agentconsul.WorkloadRestarter
	taskEnvBuilder      *taskenv.Builder
	networkStatusGetter networkStatusGetter
	logger              log.Logger
}

func newGroupServiceHook(cfg groupServiceHookConfig) *groupServiceHook {
	var shutdownDelay time.Duration
	tg := cfg.alloc.Job.LookupTaskGroup(cfg.alloc.TaskGroup)

	if tg.ShutdownDelay != nil {
		shutdownDelay = *tg.ShutdownDelay
	}

	h := &groupServiceHook{
		allocID:             cfg.alloc.ID,
		group:               cfg.alloc.TaskGroup,
		restarter:           cfg.restarter,
		consulClient:        cfg.consul,
		consulNamespace:     cfg.consulNamespace,
		taskEnvBuilder:      cfg.taskEnvBuilder,
		delay:               shutdownDelay,
		networkStatusGetter: cfg.networkStatusGetter,
		logger:              cfg.logger.Named(groupServiceHookName),
		services:            cfg.alloc.Job.LookupTaskGroup(cfg.alloc.TaskGroup).Services,
	}

	if cfg.alloc.AllocatedResources != nil {
		h.networks = cfg.alloc.AllocatedResources.Shared.Networks
		h.ports = cfg.alloc.AllocatedResources.Shared.Ports
	}

	if cfg.alloc.DeploymentStatus != nil {
		h.canary = cfg.alloc.DeploymentStatus.Canary
	}

	return h
}

func (*groupServiceHook) Name() string {
	return groupServiceHookName
}

func (h *groupServiceHook) Prerun() error {
	h.mu.Lock()
	defer func() {
		// Mark prerun as true to unblock Updates
		h.prerun = true
		h.mu.Unlock()
	}()
	return h.prerunLocked()
}

func (h *groupServiceHook) prerunLocked() error {
	if len(h.services) == 0 {
		return nil
	}

	services := h.getWorkloadServices()
	return h.consulClient.RegisterWorkload(services)
}

func (h *groupServiceHook) Update(req *interfaces.RunnerUpdateRequest) error {
	h.mu.Lock()
	defer h.mu.Unlock()

	oldWorkloadServices := h.getWorkloadServices()

	// Store new updated values out of request
	canary := false
	if req.Alloc.DeploymentStatus != nil {
		canary = req.Alloc.DeploymentStatus.Canary
	}

	var networks structs.Networks
	if req.Alloc.AllocatedResources != nil {
		networks = req.Alloc.AllocatedResources.Shared.Networks
		h.ports = req.Alloc.AllocatedResources.Shared.Ports
	}

	tg := req.Alloc.Job.LookupTaskGroup(h.group)
	var shutdown time.Duration
	if tg.ShutdownDelay != nil {
		shutdown = *tg.ShutdownDelay
	}

	// Update group service hook fields
	h.networks = networks
	h.services = tg.Services
	h.canary = canary
	h.delay = shutdown
	h.taskEnvBuilder.UpdateTask(req.Alloc, nil)

	// Create new task services struct with those new values
	newWorkloadServices := h.getWorkloadServices()

	if !h.prerun {
		// Update called before Prerun. Update alloc and exit to allow
		// Prerun to do initial registration.
		return nil
	}

	return h.consulClient.UpdateWorkload(oldWorkloadServices, newWorkloadServices)
}

func (h *groupServiceHook) PreTaskRestart() error {
	h.mu.Lock()
	defer func() {
		// Mark prerun as true to unblock Updates
		h.prerun = true
		h.mu.Unlock()
	}()

	h.preKillLocked()
	return h.prerunLocked()
}

func (h *groupServiceHook) PreKill() {
	h.mu.Lock()
	defer h.mu.Unlock()
	h.preKillLocked()
}

// implements the PreKill hook but requires the caller hold the lock
func (h *groupServiceHook) preKillLocked() {
	// If we have a shutdown delay deregister
	// group services and then wait
	// before continuing to kill tasks
	h.deregister()
	h.deregistered = true

	if h.delay == 0 {
		return
	}

	h.logger.Debug("waiting before removing group service", "shutdown_delay", h.delay)

	// Wait for specified shutdown_delay
	// this will block an agent from shutting down
	<-time.After(h.delay)
}

func (h *groupServiceHook) Postrun() error {
	h.mu.Lock()
	defer h.mu.Unlock()

	if !h.deregistered {
		h.deregister()
	}
	return nil
}

// deregister services from Consul.
func (h *groupServiceHook) deregister() {
	if len(h.services) > 0 {
		workloadServices := h.getWorkloadServices()
		h.consulClient.RemoveWorkload(workloadServices)
	}
}

func (h *groupServiceHook) getWorkloadServices() *agentconsul.WorkloadServices {
	// Interpolate with the task's environment
	interpolatedServices := taskenv.InterpolateServices(h.taskEnvBuilder.Build(), h.services)

	var netStatus *structs.AllocNetworkStatus
	if h.networkStatusGetter != nil {
		netStatus = h.networkStatusGetter.NetworkStatus()
	}

	// Create task services struct with request's driver metadata
	return &agentconsul.WorkloadServices{
		AllocID:         h.allocID,
		Group:           h.group,
		ConsulNamespace: h.consulNamespace,
		Restarter:       h.restarter,
		Services:        interpolatedServices,
		Networks:        h.networks,
		NetworkStatus:   netStatus,
		Ports:           h.ports,
		Canary:          h.canary,
	}
}
<commit_msg>client: fix logline in group shutdown hook<commit_after>package allocrunner

import (
	"sync"
	"time"

	log "github.com/hashicorp/go-hclog"
	"github.com/hashicorp/nomad/client/allocrunner/interfaces"
	"github.com/hashicorp/nomad/client/consul"
	"github.com/hashicorp/nomad/client/taskenv"
	agentconsul "github.com/hashicorp/nomad/command/agent/consul"
	"github.com/hashicorp/nomad/nomad/structs"
)

const (
	groupServiceHookName = "group_services"
)

type networkStatusGetter interface {
	NetworkStatus() *structs.AllocNetworkStatus
}

// groupServiceHook manages task group Consul service registration and
// deregistration.
type groupServiceHook struct {
	allocID             string
	group               string
	restarter           agentconsul.WorkloadRestarter
	consulClient        consul.ConsulServiceAPI
	consulNamespace     string
	prerun              bool
	delay               time.Duration
	deregistered        bool
	networkStatusGetter networkStatusGetter

	logger log.Logger

	// The following fields may be updated
	canary         bool
	services       []*structs.Service
	networks       structs.Networks
	ports          structs.AllocatedPorts
	taskEnvBuilder *taskenv.Builder

	// Since Update() may be called concurrently with any other hook all
	// hook methods must be fully serialized
	mu sync.Mutex
}

type groupServiceHookConfig struct {
	alloc               *structs.Allocation
	consul              consul.ConsulServiceAPI
	consulNamespace     string
	restarter           agentconsul.WorkloadRestarter
	taskEnvBuilder      *taskenv.Builder
	networkStatusGetter networkStatusGetter
	logger              log.Logger
}

func newGroupServiceHook(cfg groupServiceHookConfig) *groupServiceHook {
	var shutdownDelay time.Duration
	tg := cfg.alloc.Job.LookupTaskGroup(cfg.alloc.TaskGroup)

	if tg.ShutdownDelay != nil {
		shutdownDelay = *tg.ShutdownDelay
	}

	h := &groupServiceHook{
		allocID:             cfg.alloc.ID,
		group:               cfg.alloc.TaskGroup,
		restarter:           cfg.restarter,
		consulClient:        cfg.consul,
		consulNamespace:     cfg.consulNamespace,
		taskEnvBuilder:      cfg.taskEnvBuilder,
		delay:               shutdownDelay,
		networkStatusGetter: cfg.networkStatusGetter,
		logger:              cfg.logger.Named(groupServiceHookName),
		services:            cfg.alloc.Job.LookupTaskGroup(cfg.alloc.TaskGroup).Services,
	}

	if cfg.alloc.AllocatedResources != nil {
		h.networks = cfg.alloc.AllocatedResources.Shared.Networks
		h.ports = cfg.alloc.AllocatedResources.Shared.Ports
	}

	if cfg.alloc.DeploymentStatus != nil {
		h.canary = cfg.alloc.DeploymentStatus.Canary
	}

	return h
}

func (*groupServiceHook) Name() string {
	return groupServiceHookName
}

func (h *groupServiceHook) Prerun() error {
	h.mu.Lock()
	defer func() {
		// Mark prerun as true to unblock Updates
		h.prerun = true
		h.mu.Unlock()
	}()
	return h.prerunLocked()
}

func (h *groupServiceHook) prerunLocked() error {
	if len(h.services) == 0 {
		return nil
	}

	services := h.getWorkloadServices()
	return h.consulClient.RegisterWorkload(services)
}

func (h *groupServiceHook) Update(req *interfaces.RunnerUpdateRequest) error {
	h.mu.Lock()
	defer h.mu.Unlock()

	oldWorkloadServices := h.getWorkloadServices()

	// Store new updated values out of request
	canary := false
	if req.Alloc.DeploymentStatus != nil {
		canary = req.Alloc.DeploymentStatus.Canary
	}

	var networks structs.Networks
	if req.Alloc.AllocatedResources != nil {
		networks = req.Alloc.AllocatedResources.Shared.Networks
		h.ports = req.Alloc.AllocatedResources.Shared.Ports
	}

	tg := req.Alloc.Job.LookupTaskGroup(h.group)
	var shutdown time.Duration
	if tg.ShutdownDelay != nil {
		shutdown = *tg.ShutdownDelay
	}

	// Update group service hook fields
	h.networks = networks
	h.services = tg.Services
	h.canary = canary
	h.delay = shutdown
	h.taskEnvBuilder.UpdateTask(req.Alloc, nil)

	// Create new task services struct with those new values
	newWorkloadServices := h.getWorkloadServices()

	if !h.prerun {
		// Update called before Prerun. Update alloc and exit to allow
		// Prerun to do initial registration.
		return nil
	}

	return h.consulClient.UpdateWorkload(oldWorkloadServices, newWorkloadServices)
}

func (h *groupServiceHook) PreTaskRestart() error {
	h.mu.Lock()
	defer func() {
		// Mark prerun as true to unblock Updates
		h.prerun = true
		h.mu.Unlock()
	}()

	h.preKillLocked()
	return h.prerunLocked()
}

func (h *groupServiceHook) PreKill() {
	h.mu.Lock()
	defer h.mu.Unlock()
	h.preKillLocked()
}

// implements the PreKill hook but requires the caller hold the lock
func (h *groupServiceHook) preKillLocked() {
	// If we have a shutdown delay deregister group services and then wait
	// before continuing to kill tasks.
	h.deregister()
	h.deregistered = true

	if h.delay == 0 {
		return
	}

	h.logger.Debug("delay before killing tasks", "group", h.group, "shutdown_delay", h.delay)

	// Wait for specified shutdown_delay
	// This will block an agent from shutting down.
	<-time.After(h.delay)
}

func (h *groupServiceHook) Postrun() error {
	h.mu.Lock()
	defer h.mu.Unlock()

	if !h.deregistered {
		h.deregister()
	}
	return nil
}

// deregister services from Consul.
func (h *groupServiceHook) deregister() {
	if len(h.services) > 0 {
		workloadServices := h.getWorkloadServices()
		h.consulClient.RemoveWorkload(workloadServices)
	}
}

func (h *groupServiceHook) getWorkloadServices() *agentconsul.WorkloadServices {
	// Interpolate with the task's environment
	interpolatedServices := taskenv.InterpolateServices(h.taskEnvBuilder.Build(), h.services)

	var netStatus *structs.AllocNetworkStatus
	if h.networkStatusGetter != nil {
		netStatus = h.networkStatusGetter.NetworkStatus()
	}

	// Create task services struct with request's driver metadata
	return &agentconsul.WorkloadServices{
		AllocID:         h.allocID,
		Group:           h.group,
		ConsulNamespace: h.consulNamespace,
		Restarter:       h.restarter,
		Services:        interpolatedServices,
		Networks:        h.networks,
		NetworkStatus:   netStatus,
		Ports:           h.ports,
		Canary:          h.canary,
	}
}
<|endoftext|>


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

package goga

import "github.com/cpmech/gosl/utl"

// Group holds a group of solutions
type Group struct {
	Ncur    int         // number of current solutions == len(All) / 2
	All     []*Solution // current and future solutions. half part is a view to Solutions
	Indices []int       // indices of current solutions
	Pairs   [][]int     // randomly selected pairs from Indices
	Metrics *Metrics    // metrics
}

// Init initialises group
func (o *Group) Init(cpu, ncpu int, solutions []*Solution, prms *Parameters) {
	nsol := len(solutions)
	start, endp1 := (cpu*nsol)/ncpu, ((cpu+1)*nsol)/ncpu
	o.Ncur = endp1 - start
	o.All = make([]*Solution, o.Ncur*2)
	o.Indices = make([]int, o.Ncur)
	o.Pairs = utl.IntsAlloc(o.Ncur/2, 2)
	for i := 0; i < o.Ncur; i++ {
		o.All[i] = solutions[start+i]
		o.All[o.Ncur+i] = NewSolution(-i, nsol, prms)
		o.Indices[i] = i
	}
	o.Metrics = new(Metrics)
	o.Metrics.Init(len(o.All), prms)
}
<commit_msg>Implement Reset method of Group<commit_after>// Copyright 2015 The Goga Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package goga

import "github.com/cpmech/gosl/utl"

// Group holds a group of solutions
type Group struct {
	Ncur    int         // number of current solutions == len(All) / 2
	All     []*Solution // current and future solutions. half part is a view to Solutions
	Indices []int       // indices of current solutions
	Pairs   [][]int     // randomly selected pairs from Indices
	Metrics *Metrics    // metrics
}

// Init initialises group
func (o *Group) Init(cpu, ncpu int, solutions []*Solution, prms *Parameters) {
	nsol := len(solutions)
	start, endp1 := (cpu*nsol)/ncpu, ((cpu+1)*nsol)/ncpu
	o.Ncur = endp1 - start
	o.All = make([]*Solution, o.Ncur*2)
	o.Indices = make([]int, o.Ncur)
	o.Pairs = utl.IntsAlloc(o.Ncur/2, 2)
	for i := 0; i < o.Ncur; i++ {
		o.All[i] = solutions[start+i]
		o.All[o.Ncur+i] = NewSolution(-(1 + i), nsol, prms) // the index is for debugging
		o.Indices[i] = i
	}
	o.Metrics = new(Metrics)
	o.Metrics.Init(len(o.All), prms)
}

// Reset resets group data
func (o *Group) Reset(cpu, ncpu int, solutions []*Solution) {
	nsol := len(solutions)
	start := (cpu * nsol) / ncpu
	for i, pp := range o.Pairs {
		for j, _ := range pp {
			o.Pairs[i][j] = 0
		}
	}
	for i := 0; i < o.Ncur; i++ {
		o.All[i] = solutions[start+i]
		o.All[o.Ncur+i].Reset(-(1 + i)) // there is no real need for this; but helps with debugging
	}
}
<|endoftext|>


<commit_before>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

package main

// CmdUser subcommand
import (
	"errors"
	"fmt"
	"os"
	"text/tabwriter"

	"github.com/fatih/color"
	"github.com/urfave/cli"
)

// DeleteGroup ...
var DeleteGroup = cli.Command{
	Name:      "delete",
	Usage:     "Deletes a group.",
	ArgsUsage: " ",
	Description: `Deletes a group.

	  Example:
		  $ ernest group delete <group-id>
	`,
	Action: func(c *cli.Context) error {
		if len(c.Args()) < 1 {
			msg := "You should specify a group id."
			color.Red(msg)
			return errors.New(msg)
		}
		m, cfg := setup(c)
		groupid := c.Args()[0]
		err := m.DeleteGroup(cfg.Token, groupid)
		return err
	},
}

// RemoveDatacenter ...
var RemoveDatacenter = cli.Command{
	Name:      "remove-datacenter",
	Usage:     "Removes a datacenter from a group.",
	ArgsUsage: " ",
	Description: `Removes an datacenter from a group.

		Example:
		  $ ernest group remove-datacenter <datacenter-id> <group-id>
	`,
	Action: func(c *cli.Context) error {
		if len(c.Args()) < 2 {
			msg := "You should specify an datacenter id and a group id."
			color.Red(msg)
			return errors.New(msg)
		}
		m, cfg := setup(c)
		datacenterid := c.Args()[0]
		groupid := c.Args()[1]
		err := m.GroupRemoveDatacenter(cfg.Token, datacenterid, groupid)
		return err
	},
}

// AddDatacenter ...
var AddDatacenter = cli.Command{
	Name:      "add-datacenter",
	Usage:     "Adds a datacenter to a group.",
	ArgsUsage: " ",
	Description: `Adds a datacenter to a group.

	  Example:
		  $ ernest group add-datacenter <datacenter-id> <group-id>
	`,
	Action: func(c *cli.Context) error {
		if len(c.Args()) < 2 {
			msg := "You should specify a datacenter id and a group id."
			color.Red(msg)
			return errors.New(msg)
		}
		m, cfg := setup(c)
		datacenterid := c.Args()[0]
		groupid := c.Args()[1]
		err := m.GroupAddDatacenter(cfg.Token, datacenterid, groupid)
		return err
	},
}

// RemoveUser ...
var RemoveUser = cli.Command{
	Name:      "remove-user",
	Usage:     "Removes an user from a group.",
	ArgsUsage: " ",
	Description: `Removes an user from a group.

		Example:
		  $ ernest group remove-user <user-id> <group-id>
	`,
	Action: func(c *cli.Context) error {
		if len(c.Args()) < 2 {
			msg := "You should specify an user id and a group id."
			color.Red(msg)
			return errors.New(msg)
		}
		m, cfg := setup(c)
		userid := c.Args()[0]
		groupid := c.Args()[1]
		err := m.GroupRemoveUser(cfg.Token, userid, groupid)
		return err
	},
}

// AddUser ...
var AddUser = cli.Command{
	Name:      "add-user",
	Usage:     "Adds a user to a group.",
	ArgsUsage: " ",
	Description: `Adds a user to a group.

	  Example:
		  $ ernest group add-user <user-name> <group-name>
	`,
	Action: func(c *cli.Context) error {
		if len(c.Args()) < 2 {
			msg := "You should specify an user name and a group name."
			color.Red(msg)
			return errors.New(msg)
		}
		m, cfg := setup(c)
		user := c.Args()[0]
		group := c.Args()[1]
		err := m.GroupAddUser(cfg.Token, user, group)
		return err
	},
}

// CreateGroup ...
var CreateGroup = cli.Command{
	Name:      "create",
	Usage:     "Create a group.",
	ArgsUsage: " ",
	Description: `Create a group.

   Example:
    $ ernest group create <name>
	`,
	Action: func(c *cli.Context) error {
		if len(c.Args()) < 1 {
			msg := "You should specify the group name"
			color.Red(msg)
			return errors.New(msg)
		}
		m, cfg := setup(c)
		name := c.Args()[0]
		err := m.CreateGroup(cfg.Token, name)
		return err
	},
}

// ListGroups ...
var ListGroups = cli.Command{
	Name:      "list",
	Usage:     "List available groups.",
	ArgsUsage: " ",
	Description: `List available groups.

   Example:
    $ ernest group list
	`,
	Action: func(c *cli.Context) error {
		m, cfg := setup(c)
		if cfg.Token == "" {
			color.Red("Please login to perform this action")
			os.Exit(1)
		}
		groups, err := m.ListGroups(cfg.Token)
		if err != nil {
			color.Red(err.Error())
		}

		w := new(tabwriter.Writer)
		w.Init(os.Stdout, 0, 8, 0, '\t', 0)
		fmt.Fprintln(w, "NAME\tID")
		for _, group := range groups {
			str := fmt.Sprintf("%s\t%d", group.Name, group.ID)
			fmt.Fprintln(w, str)
		}
		w.Flush()
		return nil
	},
}

// CmdGroup ...
var CmdGroup = cli.Command{
	Name:  "group",
	Usage: "Group related subcommands",
	Subcommands: []cli.Command{
		DeleteGroup,
		ListGroups,
		CreateGroup,
		AddUser,
		RemoveUser,
		AddDatacenter,
		RemoveDatacenter,
	},
}
<commit_msg>Scenario: ernest group list<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

package main

// CmdUser subcommand
import (
	"errors"
	"os"
	"strconv"

	"github.com/fatih/color"
	"github.com/olekukonko/tablewriter"
	"github.com/urfave/cli"
)

// DeleteGroup ...
var DeleteGroup = cli.Command{
	Name:      "delete",
	Usage:     "Deletes a group.",
	ArgsUsage: " ",
	Description: `Deletes a group.

	  Example:
		  $ ernest group delete <group-id>
	`,
	Action: func(c *cli.Context) error {
		if len(c.Args()) < 1 {
			msg := "You should specify a group id."
			color.Red(msg)
			return errors.New(msg)
		}
		m, cfg := setup(c)
		groupid := c.Args()[0]
		err := m.DeleteGroup(cfg.Token, groupid)
		return err
	},
}

// RemoveDatacenter ...
var RemoveDatacenter = cli.Command{
	Name:      "remove-datacenter",
	Usage:     "Removes a datacenter from a group.",
	ArgsUsage: " ",
	Description: `Removes an datacenter from a group.

		Example:
		  $ ernest group remove-datacenter <datacenter-id> <group-id>
	`,
	Action: func(c *cli.Context) error {
		if len(c.Args()) < 2 {
			msg := "You should specify an datacenter id and a group id."
			color.Red(msg)
			return errors.New(msg)
		}
		m, cfg := setup(c)
		datacenterid := c.Args()[0]
		groupid := c.Args()[1]
		err := m.GroupRemoveDatacenter(cfg.Token, datacenterid, groupid)
		return err
	},
}

// AddDatacenter ...
var AddDatacenter = cli.Command{
	Name:      "add-datacenter",
	Usage:     "Adds a datacenter to a group.",
	ArgsUsage: " ",
	Description: `Adds a datacenter to a group.

	  Example:
		  $ ernest group add-datacenter <datacenter-id> <group-id>
	`,
	Action: func(c *cli.Context) error {
		if len(c.Args()) < 2 {
			msg := "You should specify a datacenter id and a group id."
			color.Red(msg)
			return errors.New(msg)
		}
		m, cfg := setup(c)
		datacenterid := c.Args()[0]
		groupid := c.Args()[1]
		err := m.GroupAddDatacenter(cfg.Token, datacenterid, groupid)
		return err
	},
}

// RemoveUser ...
var RemoveUser = cli.Command{
	Name:      "remove-user",
	Usage:     "Removes an user from a group.",
	ArgsUsage: " ",
	Description: `Removes an user from a group.

		Example:
		  $ ernest group remove-user <user-id> <group-id>
	`,
	Action: func(c *cli.Context) error {
		if len(c.Args()) < 2 {
			msg := "You should specify an user id and a group id."
			color.Red(msg)
			return errors.New(msg)
		}
		m, cfg := setup(c)
		userid := c.Args()[0]
		groupid := c.Args()[1]
		err := m.GroupRemoveUser(cfg.Token, userid, groupid)
		return err
	},
}

// AddUser ...
var AddUser = cli.Command{
	Name:      "add-user",
	Usage:     "Adds a user to a group.",
	ArgsUsage: " ",
	Description: `Adds a user to a group.

	  Example:
		  $ ernest group add-user <user-name> <group-name>
	`,
	Action: func(c *cli.Context) error {
		if len(c.Args()) < 2 {
			msg := "You should specify an user name and a group name."
			color.Red(msg)
			return errors.New(msg)
		}
		m, cfg := setup(c)
		user := c.Args()[0]
		group := c.Args()[1]
		err := m.GroupAddUser(cfg.Token, user, group)
		return err
	},
}

// CreateGroup ...
var CreateGroup = cli.Command{
	Name:      "create",
	Usage:     "Create a group.",
	ArgsUsage: " ",
	Description: `Create a group.

   Example:
    $ ernest group create <name>
	`,
	Action: func(c *cli.Context) error {
		if len(c.Args()) < 1 {
			msg := "You should specify the group name"
			color.Red(msg)
			return errors.New(msg)
		}
		m, cfg := setup(c)
		name := c.Args()[0]
		err := m.CreateGroup(cfg.Token, name)
		return err
	},
}

// ListGroups ...
var ListGroups = cli.Command{
	Name:      "list",
	Usage:     "List available groups.",
	ArgsUsage: " ",
	Description: `List available groups.

   Example:
    $ ernest group list
	`,
	Action: func(c *cli.Context) error {
		m, cfg := setup(c)
		if cfg.Token == "" {
			color.Red("You're not allowed to perform this action, please log in")
			return nil
		}
		groups, err := m.ListGroups(cfg.Token)
		if err != nil {
			color.Red("We didn't found any accessible group")
			return nil
		}

		table := tablewriter.NewWriter(os.Stdout)
		table.SetHeader([]string{"ID", "Name"})
		for _, g := range groups {
			id := strconv.Itoa(g.ID)
			table.Append([]string{id, g.Name})
		}
		table.Render()

		return nil
	},
}

// CmdGroup ...
var CmdGroup = cli.Command{
	Name:  "group",
	Usage: "Group related subcommands",
	Subcommands: []cli.Command{
		DeleteGroup,
		ListGroups,
		CreateGroup,
		AddUser,
		RemoveUser,
		AddDatacenter,
		RemoveDatacenter,
	},
}
<|endoftext|>


<commit_before>/*
Copyright 2018 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 metadata

import (
	"encoding/json"
	"fmt"
	"time"

	"k8s.io/klog"

	metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/runtime/schema"
	"k8s.io/apimachinery/pkg/runtime/serializer"
	"k8s.io/apimachinery/pkg/types"
	"k8s.io/apimachinery/pkg/watch"
	"k8s.io/client-go/rest"
)

var deleteScheme = runtime.NewScheme()
var parameterScheme = runtime.NewScheme()
var deleteOptionsCodec = serializer.NewCodecFactory(deleteScheme)
var dynamicParameterCodec = runtime.NewParameterCodec(parameterScheme)

var versionV1 = schema.GroupVersion{Version: "v1"}

func init() {
	metav1.AddToGroupVersion(parameterScheme, versionV1)
	metav1.AddToGroupVersion(deleteScheme, versionV1)
}

// Client allows callers to retrieve the object metadata for any
// Kubernetes-compatible API endpoint. The client uses the
// meta.k8s.io/v1 PartialObjectMetadata resource to more efficiently
// retrieve just the necessary metadata, but on older servers
// (Kubernetes 1.14 and before) will retrieve the object and then
// convert the metadata.
type Client struct {
	client *rest.RESTClient
}

var _ Interface = &Client{}

// ConfigFor returns a copy of the provided config with the
// appropriate metadata client defaults set.
func ConfigFor(inConfig *rest.Config) *rest.Config {
	config := rest.CopyConfig(inConfig)
	config.AcceptContentTypes = "application/vnd.kubernetes.protobuf,application/json"
	config.ContentType = "application/vnd.kubernetes.protobuf"
	config.NegotiatedSerializer = metainternalversion.Codecs.WithoutConversion()
	if config.UserAgent == "" {
		config.UserAgent = rest.DefaultKubernetesUserAgent()
	}
	return config
}

// NewForConfigOrDie creates a new metadata client for the given config and
// panics if there is an error in the config.
func NewForConfigOrDie(c *rest.Config) Interface {
	ret, err := NewForConfig(c)
	if err != nil {
		panic(err)
	}
	return ret
}

// NewForConfig creates a new metadata client that can retrieve object
// metadata details about any Kubernetes object (core, aggregated, or custom
// resource based) in the form of PartialObjectMetadata objects, or returns
// an error.
func NewForConfig(inConfig *rest.Config) (Interface, error) {
	config := ConfigFor(inConfig)
	// for serializing the options
	config.GroupVersion = &schema.GroupVersion{}
	config.APIPath = "/this-value-should-never-be-sent"

	restClient, err := rest.RESTClientFor(config)
	if err != nil {
		return nil, err
	}

	return &Client{client: restClient}, nil
}

type client struct {
	client    *Client
	namespace string
	resource  schema.GroupVersionResource
}

// Resource returns an interface that can access cluster or namespace
// scoped instances of resource.
func (c *Client) Resource(resource schema.GroupVersionResource) Getter {
	return &client{client: c, resource: resource}
}

// Namespace returns an interface that can access namespace-scoped instances of the
// provided resource.
func (c *client) Namespace(ns string) ResourceInterface {
	ret := *c
	ret.namespace = ns
	return &ret
}

// Delete removes the provided resource from the server.
func (c *client) Delete(name string, opts *metav1.DeleteOptions, subresources ...string) error {
	if len(name) == 0 {
		return fmt.Errorf("name is required")
	}
	if opts == nil {
		opts = &metav1.DeleteOptions{}
	}
	deleteOptionsByte, err := runtime.Encode(deleteOptionsCodec.LegacyCodec(schema.GroupVersion{Version: "v1"}), opts)
	if err != nil {
		return err
	}

	result := c.client.client.
		Delete().
		AbsPath(append(c.makeURLSegments(name), subresources...)...).
		Body(deleteOptionsByte).
		Do()
	return result.Error()
}

// DeleteCollection triggers deletion of all resources in the specified scope (namespace or cluster).
func (c *client) DeleteCollection(opts *metav1.DeleteOptions, listOptions metav1.ListOptions) error {
	if opts == nil {
		opts = &metav1.DeleteOptions{}
	}
	deleteOptionsByte, err := runtime.Encode(deleteOptionsCodec.LegacyCodec(schema.GroupVersion{Version: "v1"}), opts)
	if err != nil {
		return err
	}

	result := c.client.client.
		Delete().
		AbsPath(c.makeURLSegments("")...).
		Body(deleteOptionsByte).
		SpecificallyVersionedParams(&listOptions, dynamicParameterCodec, versionV1).
		Do()
	return result.Error()
}

// Get returns the resource with name from the specified scope (namespace or cluster).
func (c *client) Get(name string, opts metav1.GetOptions, subresources ...string) (*metav1.PartialObjectMetadata, error) {
	if len(name) == 0 {
		return nil, fmt.Errorf("name is required")
	}
	result := c.client.client.Get().AbsPath(append(c.makeURLSegments(name), subresources...)...).
		SetHeader("Accept", "application/vnd.kubernetes.protobuf;as=PartialObjectMetadata;g=meta.k8s.io;v=v1,application/json;as=PartialObjectMetadata;g=meta.k8s.io;v=v1,application/json").
		SpecificallyVersionedParams(&opts, dynamicParameterCodec, versionV1).
		Do()
	if err := result.Error(); err != nil {
		return nil, err
	}
	obj, err := result.Get()
	if runtime.IsNotRegisteredError(err) {
		klog.V(5).Infof("Unable to retrieve PartialObjectMetadata: %#v", err)
		rawBytes, err := result.Raw()
		if err != nil {
			return nil, err
		}
		var partial metav1.PartialObjectMetadata
		if err := json.Unmarshal(rawBytes, &partial); err != nil {
			return nil, fmt.Errorf("unable to decode returned object as PartialObjectMetadata: %v", err)
		}
		if !isLikelyObjectMetadata(&partial) {
			return nil, fmt.Errorf("object does not appear to match the ObjectMeta schema: %#v", partial)
		}
		partial.TypeMeta = metav1.TypeMeta{}
		return &partial, nil
	}
	if err != nil {
		return nil, err
	}
	partial, ok := obj.(*metav1.PartialObjectMetadata)
	if !ok {
		return nil, fmt.Errorf("unexpected object, expected PartialObjectMetadata but got %T", obj)
	}
	return partial, nil
}

// List returns all resources within the specified scope (namespace or cluster).
func (c *client) List(opts metav1.ListOptions) (*metav1.PartialObjectMetadataList, error) {
	result := c.client.client.Get().AbsPath(c.makeURLSegments("")...).
		SetHeader("Accept", "application/vnd.kubernetes.protobuf;as=PartialObjectMetadataList;g=meta.k8s.io;v=v1,application/json;as=PartialObjectMetadataList;g=meta.k8s.io;v=v1,application/json").
		SpecificallyVersionedParams(&opts, dynamicParameterCodec, versionV1).
		Do()
	if err := result.Error(); err != nil {
		return nil, err
	}
	obj, err := result.Get()
	if runtime.IsNotRegisteredError(err) {
		klog.V(5).Infof("Unable to retrieve PartialObjectMetadataList: %#v", err)
		rawBytes, err := result.Raw()
		if err != nil {
			return nil, err
		}
		var partial metav1.PartialObjectMetadataList
		if err := json.Unmarshal(rawBytes, &partial); err != nil {
			return nil, fmt.Errorf("unable to decode returned object as PartialObjectMetadataList: %v", err)
		}
		partial.TypeMeta = metav1.TypeMeta{}
		return &partial, nil
	}
	if err != nil {
		return nil, err
	}
	partial, ok := obj.(*metav1.PartialObjectMetadataList)
	if !ok {
		return nil, fmt.Errorf("unexpected object, expected PartialObjectMetadata but got %T", obj)
	}
	return partial, nil
}

// Watch finds all changes to the resources in the specified scope (namespace or cluster).
func (c *client) Watch(opts metav1.ListOptions) (watch.Interface, error) {
	var timeout time.Duration
	if opts.TimeoutSeconds != nil {
		timeout = time.Duration(*opts.TimeoutSeconds) * time.Second
	}
	opts.Watch = true
	return c.client.client.Get().
		AbsPath(c.makeURLSegments("")...).
		SetHeader("Accept", "application/vnd.kubernetes.protobuf;as=PartialObjectMetadata;g=meta.k8s.io;v=v1,application/json;as=PartialObjectMetadata;g=meta.k8s.io;v=v1,application/json").
		SpecificallyVersionedParams(&opts, dynamicParameterCodec, versionV1).
		Timeout(timeout).
		Watch()
}

// Patch modifies the named resource in the specified scope (namespace or cluster).
func (c *client) Patch(name string, pt types.PatchType, data []byte, opts metav1.PatchOptions, subresources ...string) (*metav1.PartialObjectMetadata, error) {
	if len(name) == 0 {
		return nil, fmt.Errorf("name is required")
	}
	result := c.client.client.
		Patch(pt).
		AbsPath(append(c.makeURLSegments(name), subresources...)...).
		Body(data).
		SetHeader("Accept", "application/vnd.kubernetes.protobuf;as=PartialObjectMetadata;g=meta.k8s.io;v=v1,application/json;as=PartialObjectMetadata;g=meta.k8s.io;v=v1,application/json").
		SpecificallyVersionedParams(&opts, dynamicParameterCodec, versionV1).
		Do()
	if err := result.Error(); err != nil {
		return nil, err
	}
	obj, err := result.Get()
	if runtime.IsNotRegisteredError(err) {
		rawBytes, err := result.Raw()
		if err != nil {
			return nil, err
		}
		var partial metav1.PartialObjectMetadata
		if err := json.Unmarshal(rawBytes, &partial); err != nil {
			return nil, fmt.Errorf("unable to decode returned object as PartialObjectMetadata: %v", err)
		}
		if !isLikelyObjectMetadata(&partial) {
			return nil, fmt.Errorf("object does not appear to match the ObjectMeta schema")
		}
		partial.TypeMeta = metav1.TypeMeta{}
		return &partial, nil
	}
	if err != nil {
		return nil, err
	}
	partial, ok := obj.(*metav1.PartialObjectMetadata)
	if !ok {
		return nil, fmt.Errorf("unexpected object, expected PartialObjectMetadata but got %T", obj)
	}
	return partial, nil
}

func (c *client) makeURLSegments(name string) []string {
	url := []string{}
	if len(c.resource.Group) == 0 {
		url = append(url, "api")
	} else {
		url = append(url, "apis", c.resource.Group)
	}
	url = append(url, c.resource.Version)

	if len(c.namespace) > 0 {
		url = append(url, "namespaces", c.namespace)
	}
	url = append(url, c.resource.Resource)

	if len(name) > 0 {
		url = append(url, name)
	}

	return url
}

func isLikelyObjectMetadata(meta *metav1.PartialObjectMetadata) bool {
	return len(meta.UID) > 0 || !meta.CreationTimestamp.IsZero() || len(meta.Name) > 0 || len(meta.GenerateName) > 0
}
<commit_msg>Ensure conversions are registered for metainternalversion codecs<commit_after>/*
Copyright 2018 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 metadata

import (
	"encoding/json"
	"fmt"
	"time"

	"k8s.io/klog"

	metainternalversionscheme "k8s.io/apimachinery/pkg/apis/meta/internalversion/scheme"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/runtime/schema"
	"k8s.io/apimachinery/pkg/runtime/serializer"
	"k8s.io/apimachinery/pkg/types"
	"k8s.io/apimachinery/pkg/watch"
	"k8s.io/client-go/rest"
)

var deleteScheme = runtime.NewScheme()
var parameterScheme = runtime.NewScheme()
var deleteOptionsCodec = serializer.NewCodecFactory(deleteScheme)
var dynamicParameterCodec = runtime.NewParameterCodec(parameterScheme)

var versionV1 = schema.GroupVersion{Version: "v1"}

func init() {
	metav1.AddToGroupVersion(parameterScheme, versionV1)
	metav1.AddToGroupVersion(deleteScheme, versionV1)
}

// Client allows callers to retrieve the object metadata for any
// Kubernetes-compatible API endpoint. The client uses the
// meta.k8s.io/v1 PartialObjectMetadata resource to more efficiently
// retrieve just the necessary metadata, but on older servers
// (Kubernetes 1.14 and before) will retrieve the object and then
// convert the metadata.
type Client struct {
	client *rest.RESTClient
}

var _ Interface = &Client{}

// ConfigFor returns a copy of the provided config with the
// appropriate metadata client defaults set.
func ConfigFor(inConfig *rest.Config) *rest.Config {
	config := rest.CopyConfig(inConfig)
	config.AcceptContentTypes = "application/vnd.kubernetes.protobuf,application/json"
	config.ContentType = "application/vnd.kubernetes.protobuf"
	config.NegotiatedSerializer = metainternalversionscheme.Codecs.WithoutConversion()
	if config.UserAgent == "" {
		config.UserAgent = rest.DefaultKubernetesUserAgent()
	}
	return config
}

// NewForConfigOrDie creates a new metadata client for the given config and
// panics if there is an error in the config.
func NewForConfigOrDie(c *rest.Config) Interface {
	ret, err := NewForConfig(c)
	if err != nil {
		panic(err)
	}
	return ret
}

// NewForConfig creates a new metadata client that can retrieve object
// metadata details about any Kubernetes object (core, aggregated, or custom
// resource based) in the form of PartialObjectMetadata objects, or returns
// an error.
func NewForConfig(inConfig *rest.Config) (Interface, error) {
	config := ConfigFor(inConfig)
	// for serializing the options
	config.GroupVersion = &schema.GroupVersion{}
	config.APIPath = "/this-value-should-never-be-sent"

	restClient, err := rest.RESTClientFor(config)
	if err != nil {
		return nil, err
	}

	return &Client{client: restClient}, nil
}

type client struct {
	client    *Client
	namespace string
	resource  schema.GroupVersionResource
}

// Resource returns an interface that can access cluster or namespace
// scoped instances of resource.
func (c *Client) Resource(resource schema.GroupVersionResource) Getter {
	return &client{client: c, resource: resource}
}

// Namespace returns an interface that can access namespace-scoped instances of the
// provided resource.
func (c *client) Namespace(ns string) ResourceInterface {
	ret := *c
	ret.namespace = ns
	return &ret
}

// Delete removes the provided resource from the server.
func (c *client) Delete(name string, opts *metav1.DeleteOptions, subresources ...string) error {
	if len(name) == 0 {
		return fmt.Errorf("name is required")
	}
	if opts == nil {
		opts = &metav1.DeleteOptions{}
	}
	deleteOptionsByte, err := runtime.Encode(deleteOptionsCodec.LegacyCodec(schema.GroupVersion{Version: "v1"}), opts)
	if err != nil {
		return err
	}

	result := c.client.client.
		Delete().
		AbsPath(append(c.makeURLSegments(name), subresources...)...).
		Body(deleteOptionsByte).
		Do()
	return result.Error()
}

// DeleteCollection triggers deletion of all resources in the specified scope (namespace or cluster).
func (c *client) DeleteCollection(opts *metav1.DeleteOptions, listOptions metav1.ListOptions) error {
	if opts == nil {
		opts = &metav1.DeleteOptions{}
	}
	deleteOptionsByte, err := runtime.Encode(deleteOptionsCodec.LegacyCodec(schema.GroupVersion{Version: "v1"}), opts)
	if err != nil {
		return err
	}

	result := c.client.client.
		Delete().
		AbsPath(c.makeURLSegments("")...).
		Body(deleteOptionsByte).
		SpecificallyVersionedParams(&listOptions, dynamicParameterCodec, versionV1).
		Do()
	return result.Error()
}

// Get returns the resource with name from the specified scope (namespace or cluster).
func (c *client) Get(name string, opts metav1.GetOptions, subresources ...string) (*metav1.PartialObjectMetadata, error) {
	if len(name) == 0 {
		return nil, fmt.Errorf("name is required")
	}
	result := c.client.client.Get().AbsPath(append(c.makeURLSegments(name), subresources...)...).
		SetHeader("Accept", "application/vnd.kubernetes.protobuf;as=PartialObjectMetadata;g=meta.k8s.io;v=v1,application/json;as=PartialObjectMetadata;g=meta.k8s.io;v=v1,application/json").
		SpecificallyVersionedParams(&opts, dynamicParameterCodec, versionV1).
		Do()
	if err := result.Error(); err != nil {
		return nil, err
	}
	obj, err := result.Get()
	if runtime.IsNotRegisteredError(err) {
		klog.V(5).Infof("Unable to retrieve PartialObjectMetadata: %#v", err)
		rawBytes, err := result.Raw()
		if err != nil {
			return nil, err
		}
		var partial metav1.PartialObjectMetadata
		if err := json.Unmarshal(rawBytes, &partial); err != nil {
			return nil, fmt.Errorf("unable to decode returned object as PartialObjectMetadata: %v", err)
		}
		if !isLikelyObjectMetadata(&partial) {
			return nil, fmt.Errorf("object does not appear to match the ObjectMeta schema: %#v", partial)
		}
		partial.TypeMeta = metav1.TypeMeta{}
		return &partial, nil
	}
	if err != nil {
		return nil, err
	}
	partial, ok := obj.(*metav1.PartialObjectMetadata)
	if !ok {
		return nil, fmt.Errorf("unexpected object, expected PartialObjectMetadata but got %T", obj)
	}
	return partial, nil
}

// List returns all resources within the specified scope (namespace or cluster).
func (c *client) List(opts metav1.ListOptions) (*metav1.PartialObjectMetadataList, error) {
	result := c.client.client.Get().AbsPath(c.makeURLSegments("")...).
		SetHeader("Accept", "application/vnd.kubernetes.protobuf;as=PartialObjectMetadataList;g=meta.k8s.io;v=v1,application/json;as=PartialObjectMetadataList;g=meta.k8s.io;v=v1,application/json").
		SpecificallyVersionedParams(&opts, dynamicParameterCodec, versionV1).
		Do()
	if err := result.Error(); err != nil {
		return nil, err
	}
	obj, err := result.Get()
	if runtime.IsNotRegisteredError(err) {
		klog.V(5).Infof("Unable to retrieve PartialObjectMetadataList: %#v", err)
		rawBytes, err := result.Raw()
		if err != nil {
			return nil, err
		}
		var partial metav1.PartialObjectMetadataList
		if err := json.Unmarshal(rawBytes, &partial); err != nil {
			return nil, fmt.Errorf("unable to decode returned object as PartialObjectMetadataList: %v", err)
		}
		partial.TypeMeta = metav1.TypeMeta{}
		return &partial, nil
	}
	if err != nil {
		return nil, err
	}
	partial, ok := obj.(*metav1.PartialObjectMetadataList)
	if !ok {
		return nil, fmt.Errorf("unexpected object, expected PartialObjectMetadata but got %T", obj)
	}
	return partial, nil
}

// Watch finds all changes to the resources in the specified scope (namespace or cluster).
func (c *client) Watch(opts metav1.ListOptions) (watch.Interface, error) {
	var timeout time.Duration
	if opts.TimeoutSeconds != nil {
		timeout = time.Duration(*opts.TimeoutSeconds) * time.Second
	}
	opts.Watch = true
	return c.client.client.Get().
		AbsPath(c.makeURLSegments("")...).
		SetHeader("Accept", "application/vnd.kubernetes.protobuf;as=PartialObjectMetadata;g=meta.k8s.io;v=v1,application/json;as=PartialObjectMetadata;g=meta.k8s.io;v=v1,application/json").
		SpecificallyVersionedParams(&opts, dynamicParameterCodec, versionV1).
		Timeout(timeout).
		Watch()
}

// Patch modifies the named resource in the specified scope (namespace or cluster).
func (c *client) Patch(name string, pt types.PatchType, data []byte, opts metav1.PatchOptions, subresources ...string) (*metav1.PartialObjectMetadata, error) {
	if len(name) == 0 {
		return nil, fmt.Errorf("name is required")
	}
	result := c.client.client.
		Patch(pt).
		AbsPath(append(c.makeURLSegments(name), subresources...)...).
		Body(data).
		SetHeader("Accept", "application/vnd.kubernetes.protobuf;as=PartialObjectMetadata;g=meta.k8s.io;v=v1,application/json;as=PartialObjectMetadata;g=meta.k8s.io;v=v1,application/json").
		SpecificallyVersionedParams(&opts, dynamicParameterCodec, versionV1).
		Do()
	if err := result.Error(); err != nil {
		return nil, err
	}
	obj, err := result.Get()
	if runtime.IsNotRegisteredError(err) {
		rawBytes, err := result.Raw()
		if err != nil {
			return nil, err
		}
		var partial metav1.PartialObjectMetadata
		if err := json.Unmarshal(rawBytes, &partial); err != nil {
			return nil, fmt.Errorf("unable to decode returned object as PartialObjectMetadata: %v", err)
		}
		if !isLikelyObjectMetadata(&partial) {
			return nil, fmt.Errorf("object does not appear to match the ObjectMeta schema")
		}
		partial.TypeMeta = metav1.TypeMeta{}
		return &partial, nil
	}
	if err != nil {
		return nil, err
	}
	partial, ok := obj.(*metav1.PartialObjectMetadata)
	if !ok {
		return nil, fmt.Errorf("unexpected object, expected PartialObjectMetadata but got %T", obj)
	}
	return partial, nil
}

func (c *client) makeURLSegments(name string) []string {
	url := []string{}
	if len(c.resource.Group) == 0 {
		url = append(url, "api")
	} else {
		url = append(url, "apis", c.resource.Group)
	}
	url = append(url, c.resource.Version)

	if len(c.namespace) > 0 {
		url = append(url, "namespaces", c.namespace)
	}
	url = append(url, c.resource.Resource)

	if len(name) > 0 {
		url = append(url, name)
	}

	return url
}

func isLikelyObjectMetadata(meta *metav1.PartialObjectMetadata) bool {
	return len(meta.UID) > 0 || !meta.CreationTimestamp.IsZero() || len(meta.Name) > 0 || len(meta.GenerateName) > 0
}
<|endoftext|>


<commit_before>package metainfo

import (
	"crypto/sha1"
	"errors"
	"fmt"
	"io"
	"log"
	"os"
	"path/filepath"
	"strings"
	"time"

	"github.com/anacrolix/missinggo"

	"github.com/anacrolix/torrent/bencode"
)

// Information specific to a single file inside the MetaInfo structure.
type FileInfo struct {
	Length int64    `bencode:"length"`
	Path   []string `bencode:"path"`
}

// Load a MetaInfo from an io.Reader. Returns a non-nil error in case of
// failure.
func Load(r io.Reader) (*MetaInfo, error) {
	var mi MetaInfo
	d := bencode.NewDecoder(r)
	err := d.Decode(&mi)
	if err != nil {
		return nil, err
	}
	return &mi, nil
}

// Convenience function for loading a MetaInfo from a file.
func LoadFromFile(filename string) (*MetaInfo, error) {
	f, err := os.Open(filename)
	if err != nil {
		return nil, err
	}
	defer f.Close()
	return Load(f)
}

// The info dictionary.
type Info struct {
	PieceLength int64      `bencode:"piece length"`
	Pieces      []byte     `bencode:"pieces"`
	Name        string     `bencode:"name"`
	Length      int64      `bencode:"length,omitempty"`
	Private     *bool      `bencode:"private,omitempty"`
	Files       []FileInfo `bencode:"files,omitempty"`
}

func (info *Info) BuildFromFilePath(root string) (err error) {
	info.Name = filepath.Base(root)
	info.Files = nil
	err = filepath.Walk(root, func(path string, fi os.FileInfo, err error) error {
		log.Println(path, root, err)
		if fi.IsDir() {
			// Directories are implicit in torrent files.
			return nil
		} else if path == root {
			// The root is a file.
			info.Length = fi.Size()
			return nil
		}
		relPath, err := filepath.Rel(root, path)
		log.Println(relPath, err)
		if err != nil {
			return fmt.Errorf("error getting relative path: %s", err)
		}
		info.Files = append(info.Files, FileInfo{
			Path:   strings.Split(relPath, string(filepath.Separator)),
			Length: fi.Size(),
		})
		return nil
	})
	if err != nil {
		return
	}
	err = info.GeneratePieces(func(fi FileInfo) (io.ReadCloser, error) {
		return os.Open(filepath.Join(root, strings.Join(fi.Path, string(filepath.Separator))))
	})
	if err != nil {
		err = fmt.Errorf("error generating pieces: %s", err)
	}
	return
}

func (info *Info) writeFiles(w io.Writer, open func(fi FileInfo) (io.ReadCloser, error)) error {
	for _, fi := range info.UpvertedFiles() {
		r, err := open(fi)
		if err != nil {
			return fmt.Errorf("error opening %v: %s", fi, err)
		}
		wn, err := io.CopyN(w, r, fi.Length)
		r.Close()
		if wn != fi.Length || err != nil {
			return fmt.Errorf("error hashing %v: %s", fi, err)
		}
	}
	return nil
}

// Set info.Pieces by hashing info.Files.
func (info *Info) GeneratePieces(open func(fi FileInfo) (io.ReadCloser, error)) error {
	if info.PieceLength == 0 {
		return errors.New("piece length must be non-zero")
	}
	pr, pw := io.Pipe()
	go func() {
		err := info.writeFiles(pw, open)
		pw.CloseWithError(err)
	}()
	defer pr.Close()
	var pieces []byte
	for {
		hasher := sha1.New()
		wn, err := io.CopyN(hasher, pr, info.PieceLength)
		if err == io.EOF {
			err = nil
		}
		if err != nil {
			return err
		}
		if wn == 0 {
			break
		}
		pieces = hasher.Sum(pieces)
		if wn < info.PieceLength {
			break
		}
	}
	info.Pieces = pieces
	return nil
}

func (info *Info) TotalLength() (ret int64) {
	if info.IsDir() {
		for _, fi := range info.Files {
			ret += fi.Length
		}
	} else {
		ret = info.Length
	}
	return
}

func (info *Info) NumPieces() int {
	if len(info.Pieces)%20 != 0 {
		panic(len(info.Pieces))
	}
	return len(info.Pieces) / 20
}

func (info *InfoEx) Piece(i int) Piece {
	return Piece{info, i}
}

func (info *Info) IsDir() bool {
	return len(info.Files) != 0
}

// The files field, converted up from the old single-file in the parent info
// dict if necessary. This is a helper to avoid having to conditionally handle
// single and multi-file torrent infos.
func (info *Info) UpvertedFiles() []FileInfo {
	if len(info.Files) == 0 {
		return []FileInfo{{
			Length: info.Length,
			// Callers should determine that Info.Name is the basename, and
			// thus a regular file.
			Path: nil,
		}}
	}
	return info.Files
}

// The info dictionary with its hash and raw bytes exposed, as these are
// important to Bittorrent.
type InfoEx struct {
	Info
	Hash  Hash   // Only set when unmarshalling or UpdateHash.
	Bytes []byte // Only set when unmarshalling or UpdateBytes.
}

var (
	_ bencode.Marshaler   = InfoEx{}
	_ bencode.Unmarshaler = &InfoEx{}
)

func (ie *InfoEx) UnmarshalBencode(data []byte) error {
	ie.Bytes = append([]byte(nil), data...)
	h := sha1.New()
	_, err := h.Write(ie.Bytes)
	if err != nil {
		panic(err)
	}
	missinggo.CopyExact(&ie.Hash, h.Sum(nil))
	return bencode.Unmarshal(data, &ie.Info)
}

func (ie InfoEx) MarshalBencode() ([]byte, error) {
	return bencode.Marshal(&ie.Info)
}

type MetaInfo struct {
	Info         InfoEx      `bencode:"info"`
	Announce     string      `bencode:"announce,omitempty"`
	AnnounceList [][]string  `bencode:"announce-list,omitempty"`
	Nodes        []Node      `bencode:"nodes,omitempty"`
	CreationDate int64       `bencode:"creation date,omitempty"`
	Comment      string      `bencode:"comment,omitempty"`
	CreatedBy    string      `bencode:"created by,omitempty"`
	Encoding     string      `bencode:"encoding,omitempty"`
	URLList      interface{} `bencode:"url-list,omitempty"`
}

// Encode to bencoded form.
func (mi *MetaInfo) Write(w io.Writer) error {
	return bencode.NewEncoder(w).Encode(mi)
}

// Set good default values in preparation for creating a new MetaInfo file.
func (mi *MetaInfo) SetDefaults() {
	mi.Comment = "yoloham"
	mi.CreatedBy = "github.com/anacrolix/torrent"
	mi.CreationDate = time.Now().Unix()
	mi.Info.PieceLength = 256 * 1024
}

// Magnetize creates a Magnet from a MetaInfo
func (mi *MetaInfo) Magnet() (m Magnet) {
	for _, tier := range mi.AnnounceList {
		for _, tracker := range tier {
			m.Trackers = append(m.Trackers, tracker)
		}
	}
	m.DisplayName = mi.Info.Name
	m.InfoHash = mi.Info.Hash
	return
}
<commit_msg>Doc metainfo.Info.BuildFromFilePath<commit_after>package metainfo

import (
	"crypto/sha1"
	"errors"
	"fmt"
	"io"
	"log"
	"os"
	"path/filepath"
	"strings"
	"time"

	"github.com/anacrolix/missinggo"

	"github.com/anacrolix/torrent/bencode"
)

// Information specific to a single file inside the MetaInfo structure.
type FileInfo struct {
	Length int64    `bencode:"length"`
	Path   []string `bencode:"path"`
}

// Load a MetaInfo from an io.Reader. Returns a non-nil error in case of
// failure.
func Load(r io.Reader) (*MetaInfo, error) {
	var mi MetaInfo
	d := bencode.NewDecoder(r)
	err := d.Decode(&mi)
	if err != nil {
		return nil, err
	}
	return &mi, nil
}

// Convenience function for loading a MetaInfo from a file.
func LoadFromFile(filename string) (*MetaInfo, error) {
	f, err := os.Open(filename)
	if err != nil {
		return nil, err
	}
	defer f.Close()
	return Load(f)
}

// The info dictionary.
type Info struct {
	PieceLength int64      `bencode:"piece length"`
	Pieces      []byte     `bencode:"pieces"`
	Name        string     `bencode:"name"`
	Length      int64      `bencode:"length,omitempty"`
	Private     *bool      `bencode:"private,omitempty"`
	Files       []FileInfo `bencode:"files,omitempty"`
}

// This is a helper that sets Files and Pieces from a root path and its
// children.
func (info *Info) BuildFromFilePath(root string) (err error) {
	info.Name = filepath.Base(root)
	info.Files = nil
	err = filepath.Walk(root, func(path string, fi os.FileInfo, err error) error {
		log.Println(path, root, err)
		if fi.IsDir() {
			// Directories are implicit in torrent files.
			return nil
		} else if path == root {
			// The root is a file.
			info.Length = fi.Size()
			return nil
		}
		relPath, err := filepath.Rel(root, path)
		log.Println(relPath, err)
		if err != nil {
			return fmt.Errorf("error getting relative path: %s", err)
		}
		info.Files = append(info.Files, FileInfo{
			Path:   strings.Split(relPath, string(filepath.Separator)),
			Length: fi.Size(),
		})
		return nil
	})
	if err != nil {
		return
	}
	err = info.GeneratePieces(func(fi FileInfo) (io.ReadCloser, error) {
		return os.Open(filepath.Join(root, strings.Join(fi.Path, string(filepath.Separator))))
	})
	if err != nil {
		err = fmt.Errorf("error generating pieces: %s", err)
	}
	return
}

func (info *Info) writeFiles(w io.Writer, open func(fi FileInfo) (io.ReadCloser, error)) error {
	for _, fi := range info.UpvertedFiles() {
		r, err := open(fi)
		if err != nil {
			return fmt.Errorf("error opening %v: %s", fi, err)
		}
		wn, err := io.CopyN(w, r, fi.Length)
		r.Close()
		if wn != fi.Length || err != nil {
			return fmt.Errorf("error hashing %v: %s", fi, err)
		}
	}
	return nil
}

// Set info.Pieces by hashing info.Files.
func (info *Info) GeneratePieces(open func(fi FileInfo) (io.ReadCloser, error)) error {
	if info.PieceLength == 0 {
		return errors.New("piece length must be non-zero")
	}
	pr, pw := io.Pipe()
	go func() {
		err := info.writeFiles(pw, open)
		pw.CloseWithError(err)
	}()
	defer pr.Close()
	var pieces []byte
	for {
		hasher := sha1.New()
		wn, err := io.CopyN(hasher, pr, info.PieceLength)
		if err == io.EOF {
			err = nil
		}
		if err != nil {
			return err
		}
		if wn == 0 {
			break
		}
		pieces = hasher.Sum(pieces)
		if wn < info.PieceLength {
			break
		}
	}
	info.Pieces = pieces
	return nil
}

func (info *Info) TotalLength() (ret int64) {
	if info.IsDir() {
		for _, fi := range info.Files {
			ret += fi.Length
		}
	} else {
		ret = info.Length
	}
	return
}

func (info *Info) NumPieces() int {
	if len(info.Pieces)%20 != 0 {
		panic(len(info.Pieces))
	}
	return len(info.Pieces) / 20
}

func (info *InfoEx) Piece(i int) Piece {
	return Piece{info, i}
}

func (info *Info) IsDir() bool {
	return len(info.Files) != 0
}

// The files field, converted up from the old single-file in the parent info
// dict if necessary. This is a helper to avoid having to conditionally handle
// single and multi-file torrent infos.
func (info *Info) UpvertedFiles() []FileInfo {
	if len(info.Files) == 0 {
		return []FileInfo{{
			Length: info.Length,
			// Callers should determine that Info.Name is the basename, and
			// thus a regular file.
			Path: nil,
		}}
	}
	return info.Files
}

// The info dictionary with its hash and raw bytes exposed, as these are
// important to Bittorrent.
type InfoEx struct {
	Info
	Hash  Hash   // Only set when unmarshalling or UpdateHash.
	Bytes []byte // Only set when unmarshalling or UpdateBytes.
}

var (
	_ bencode.Marshaler   = InfoEx{}
	_ bencode.Unmarshaler = &InfoEx{}
)

func (ie *InfoEx) UnmarshalBencode(data []byte) error {
	ie.Bytes = append([]byte(nil), data...)
	h := sha1.New()
	_, err := h.Write(ie.Bytes)
	if err != nil {
		panic(err)
	}
	missinggo.CopyExact(&ie.Hash, h.Sum(nil))
	return bencode.Unmarshal(data, &ie.Info)
}

func (ie InfoEx) MarshalBencode() ([]byte, error) {
	return bencode.Marshal(&ie.Info)
}

type MetaInfo struct {
	Info         InfoEx      `bencode:"info"`
	Announce     string      `bencode:"announce,omitempty"`
	AnnounceList [][]string  `bencode:"announce-list,omitempty"`
	Nodes        []Node      `bencode:"nodes,omitempty"`
	CreationDate int64       `bencode:"creation date,omitempty"`
	Comment      string      `bencode:"comment,omitempty"`
	CreatedBy    string      `bencode:"created by,omitempty"`
	Encoding     string      `bencode:"encoding,omitempty"`
	URLList      interface{} `bencode:"url-list,omitempty"`
}

// Encode to bencoded form.
func (mi *MetaInfo) Write(w io.Writer) error {
	return bencode.NewEncoder(w).Encode(mi)
}

// Set good default values in preparation for creating a new MetaInfo file.
func (mi *MetaInfo) SetDefaults() {
	mi.Comment = "yoloham"
	mi.CreatedBy = "github.com/anacrolix/torrent"
	mi.CreationDate = time.Now().Unix()
	mi.Info.PieceLength = 256 * 1024
}

// Magnetize creates a Magnet from a MetaInfo
func (mi *MetaInfo) Magnet() (m Magnet) {
	for _, tier := range mi.AnnounceList {
		for _, tracker := range tier {
			m.Trackers = append(m.Trackers, tracker)
		}
	}
	m.DisplayName = mi.Info.Name
	m.InfoHash = mi.Info.Hash
	return
}
<|endoftext|>