コード例 #1
0
ファイル: server.go プロジェクト: weaveworks/mesh
func mustPeerName() mesh.PeerName {
	peerName, err := mesh.PeerNameFromString(mustHardwareAddr())
	if err != nil {
		panic(err)
	}
	return peerName
}
コード例 #2
0
ファイル: testutils_test.go プロジェクト: mandubian/weave
func ExpectMessage(alloc *Allocator, dst string, msgType byte, buf []byte) {
	m := alloc.gossip.(*mockGossipComms)
	dstPeerName, _ := mesh.PeerNameFromString(dst)
	m.Lock()
	m.messages = append(m.messages, mockMessage{dstPeerName, msgType, buf})
	m.Unlock()
}
コード例 #3
0
ファイル: main.go プロジェクト: prometheus/alertmanager
func initMesh(addr, hwaddr, nickname, pw string) *mesh.Router {
	host, portStr, err := net.SplitHostPort(addr)

	if err != nil {
		log.Fatalf("mesh address: %s: %v", addr, err)
	}
	port, err := strconv.Atoi(portStr)
	if err != nil {
		log.Fatalf("mesh address: %s: %v", addr, err)
	}

	name, err := mesh.PeerNameFromString(hwaddr)
	if err != nil {
		log.Fatalf("invalid hardware address %q: %v", hwaddr, err)
	}

	password := []byte(pw)
	if len(password) == 0 {
		// Emtpy password is used to disable secure communication. Using a nil
		// password disables encryption in mesh.
		password = nil
	}

	return mesh.NewRouter(mesh.Config{
		Host:               host,
		Port:               port,
		ProtocolMinVersion: mesh.ProtocolMinVersion,
		Password:           password,
		ConnLimit:          64,
		PeerDiscovery:      true,
		TrustedSubnets:     []*net.IPNet{},
	}, name, nickname, mesh.NullOverlay{}, stdlog.New(ioutil.Discard, "", 0))

}
コード例 #4
0
ファイル: nameserver_test.go プロジェクト: brb/weave
func TestTombstoneDeletion(t *testing.T) {
	oldNow := now
	defer func() { now = oldNow }()
	now = func() int64 { return 1234 }

	peername, err := mesh.PeerNameFromString("00:00:00:02:00:00")
	require.Nil(t, err)
	nameserver := makeNameserver(peername)

	nameserver.AddEntry("hostname", "containerid", peername, address.Address(0))
	require.Equal(t, []address.Address{0}, nameserver.Lookup("hostname"))

	nameserver.deleteTombstones()
	require.Equal(t, []address.Address{0}, nameserver.Lookup("hostname"))

	nameserver.Delete("hostname", "containerid", "", address.Address(0))
	require.Equal(t, []address.Address{}, nameserver.Lookup("hostname"))
	require.Equal(t, l(Entries{Entry{
		ContainerID: "containerid",
		Origin:      peername,
		Addr:        address.Address(0),
		Hostname:    "hostname",
		Version:     1,
		Tombstone:   1234,
	}}), nameserver.entries)

	now = func() int64 { return 1234 + int64(tombstoneTimeout/time.Second) + 1 }
	nameserver.deleteTombstones()
	require.Equal(t, Entries{}, nameserver.entries)
}
コード例 #5
0
ファイル: main.go プロジェクト: farcaller/alertmanager
func initMesh(addr, hwaddr, nickname string) *mesh.Router {
	host, portStr, err := net.SplitHostPort(addr)

	if err != nil {
		log.Fatalf("mesh address: %s: %v", addr, err)
	}
	port, err := strconv.Atoi(portStr)
	if err != nil {
		log.Fatalf("mesh address: %s: %v", addr, err)
	}

	name, err := mesh.PeerNameFromString(hwaddr)
	if err != nil {
		log.Fatalf("invalid hardware address %q: %v", hwaddr, err)
	}

	return mesh.NewRouter(mesh.Config{
		Host:               host,
		Port:               port,
		ProtocolMinVersion: mesh.ProtocolMinVersion,
		Password:           []byte(""),
		ConnLimit:          64,
		PeerDiscovery:      true,
		TrustedSubnets:     []*net.IPNet{},
	}, name, nickname, mesh.NullOverlay{}, stdlog.New(ioutil.Discard, "", 0))

}
コード例 #6
0
ファイル: ring_test.go プロジェクト: brb/weave
func makePeerName(i int) mesh.PeerName {
	if i >= 10000 {
		panic("makePeerName: invalid value")
	}
	peer, _ := mesh.PeerNameFromString(fmt.Sprintf("%02d:%02d:00:00:00:ff", i/100, i%100))
	return peer
}
コード例 #7
0
ファイル: allocator.go プロジェクト: ybalt/weave
// Lookup a PeerName by nickname or stringified PeerName.  We can't
// call into the router for this because we are interested in peers
// that have gone away but are still in the ring, which is why we
// maintain our own nicknames map.
func (alloc *Allocator) lookupPeername(name string) (mesh.PeerName, error) {
	for peername, nickname := range alloc.nicknames {
		if nickname == name {
			return peername, nil
		}
	}

	return mesh.PeerNameFromString(name)
}
コード例 #8
0
ファイル: dns_test.go プロジェクト: n054/weave
func startServer(t *testing.T, upstream *dns.ClientConfig) (*DNSServer, *Nameserver, int, int) {
	peername, err := mesh.PeerNameFromString("00:00:00:02:00:00")
	require.Nil(t, err)
	nameserver := New(peername, "", func(mesh.PeerName) bool { return true })
	dnsserver, err := NewDNSServer(nameserver, "weave.local.", "0.0.0.0:0", &mockUpstream{upstream}, 30, 5*time.Second)
	require.Nil(t, err)
	udpPort := dnsserver.servers[0].PacketConn.LocalAddr().(*net.UDPAddr).Port
	tcpPort := dnsserver.servers[1].Listener.Addr().(*net.TCPAddr).Port
	go dnsserver.ActivateAndServe()
	return dnsserver, nameserver, udpPort, tcpPort
}
コード例 #9
0
ファイル: nameserver_test.go プロジェクト: brb/weave
func makeNetwork(size int) ([]*Nameserver, *gossip.TestRouter) {
	gossipRouter := gossip.NewTestRouter(0.0)
	nameservers := make([]*Nameserver, size)

	for i := 0; i < size; i++ {
		name, _ := mesh.PeerNameFromString(fmt.Sprintf("%02d:00:00:02:00:00", i))
		nameserver := makeNameserver(name)
		nameserver.SetGossip(gossipRouter.Connect(nameserver.ourName, nameserver))
		nameserver.Start()
		nameservers[i] = nameserver
	}

	return nameservers, gossipRouter
}
コード例 #10
0
ファイル: testutils_test.go プロジェクト: mandubian/weave
func makeAllocator(name string, cidrStr string, quorum uint) (*Allocator, address.Range) {
	peername, err := mesh.PeerNameFromString(name)
	if err != nil {
		panic(err)
	}

	_, cidr, err := address.ParseCIDR(cidrStr)
	if err != nil {
		panic(err)
	}

	alloc := NewAllocator(peername, mesh.PeerUID(rand.Int63()),
		"nick-"+name, cidr.Range(), quorum, func(mesh.PeerName) bool { return true })

	return alloc, cidr.HostRange()
}
コード例 #11
0
ファイル: nameserver_test.go プロジェクト: brb/weave
func TestContainerAndPeerDeath(t *testing.T) {
	peername, err := mesh.PeerNameFromString("00:00:00:02:00:00")
	require.Nil(t, err)
	nameserver := makeNameserver(peername)

	nameserver.AddEntry("hostname", "containerid", peername, address.Address(0))
	require.Equal(t, []address.Address{0}, nameserver.Lookup("hostname"))

	nameserver.ContainerDied("containerid")
	require.Equal(t, []address.Address{}, nameserver.Lookup("hostname"))

	nameserver.AddEntry("hostname", "containerid", peername, address.Address(0))
	require.Equal(t, []address.Address{0}, nameserver.Lookup("hostname"))

	nameserver.PeerGone(peername)
	require.Equal(t, []address.Address{}, nameserver.Lookup("hostname"))
}
コード例 #12
0
ファイル: ctrl_test.go プロジェクト: weaveworks/mesh
func TestCtrlTerminates(t *testing.T) {
	var (
		peerName, _  = mesh.PeerNameFromString("01:23:45:67:89:01")
		self         = meshconn.MeshAddr{PeerName: peerName, PeerUID: 123}
		others       = []net.Addr{}
		minPeerCount = 5
		incomingc    = make(chan raftpb.Message)
		outgoingc    = make(chan raftpb.Message, 10000)
		unreachablec = make(chan uint64)
		confchangec  = make(chan raftpb.ConfChange)
		snapshotc    = make(chan raftpb.Snapshot, 10000)
		entryc       = make(chan raftpb.Entry)
		proposalc    = make(chan []byte)
		removedc     = make(chan struct{})
		logger       = log.New(os.Stderr, "", log.LstdFlags)
	)
	c := newCtrl(
		self,
		others,
		minPeerCount,
		incomingc,
		outgoingc,
		unreachablec,
		confchangec,
		snapshotc,
		entryc,
		proposalc,
		removedc,
		logger,
	)
	stopped := make(chan struct{})
	go func() {
		c.stop()
		close(stopped)
	}()
	select {
	case <-stopped:
		t.Log("ctrl terminated")
	case <-time.After(5 * time.Second):
		t.Fatal("ctrl didn't terminate")
	}
}
コード例 #13
0
ファイル: peer_name_mac_test.go プロジェクト: weaveworks/mesh
func checkSuccess(t *testing.T, nameStr string, expected uint64) {
	actual, err := mesh.PeerNameFromString(nameStr)
	require.NoError(t, err)
	require.Equal(t, mesh.PeerName(expected), actual)
}
コード例 #14
0
ファイル: main.go プロジェクト: weaveworks/mesh
func main() {
	peers := &stringset{}
	var (
		httpListen = flag.String("http", ":8080", "HTTP listen address")
		meshListen = flag.String("mesh", net.JoinHostPort("0.0.0.0", strconv.Itoa(mesh.Port)), "mesh listen address")
		hwaddr     = flag.String("hwaddr", mustHardwareAddr(), "MAC address, i.e. mesh peer ID")
		nickname   = flag.String("nickname", mustHostname(), "peer nickname")
		password   = flag.String("password", "", "password (optional)")
		channel    = flag.String("channel", "default", "gossip channel name")
	)
	flag.Var(peers, "peer", "initial peer (may be repeated)")
	flag.Parse()

	logger := log.New(os.Stderr, *nickname+"> ", log.LstdFlags)

	host, portStr, err := net.SplitHostPort(*meshListen)
	if err != nil {
		logger.Fatalf("mesh address: %s: %v", *meshListen, err)
	}
	port, err := strconv.Atoi(portStr)
	if err != nil {
		logger.Fatalf("mesh address: %s: %v", *meshListen, err)
	}

	name, err := mesh.PeerNameFromString(*hwaddr)
	if err != nil {
		logger.Fatalf("%s: %v", *hwaddr, err)
	}

	router := mesh.NewRouter(mesh.Config{
		Host:               host,
		Port:               port,
		ProtocolMinVersion: mesh.ProtocolMinVersion,
		Password:           []byte(*password),
		ConnLimit:          64,
		PeerDiscovery:      true,
		TrustedSubnets:     []*net.IPNet{},
	}, name, *nickname, mesh.NullOverlay{}, log.New(ioutil.Discard, "", 0))

	peer := newPeer(name, logger)
	gossip := router.NewGossip(*channel, peer)
	peer.register(gossip)

	func() {
		logger.Printf("mesh router starting (%s)", *meshListen)
		router.Start()
	}()
	defer func() {
		logger.Printf("mesh router stopping")
		router.Stop()
	}()

	router.ConnectionMaker.InitiateConnections(peers.slice(), true)

	errs := make(chan error)
	go func() {
		c := make(chan os.Signal)
		signal.Notify(c, syscall.SIGINT)
		errs <- fmt.Errorf("%s", <-c)
	}()
	go func() {
		logger.Printf("HTTP server starting (%s)", *httpListen)
		http.HandleFunc("/", handle(peer))
		errs <- http.ListenAndServe(*httpListen, nil)
	}()
	logger.Print(<-errs)
}
コード例 #15
0
ファイル: main.go プロジェクト: weaveworks/mesh
func main() {
	peers := &stringset{}
	var (
		apiListen  = flag.String("api", ":8080", "API listen address")
		meshListen = flag.String("mesh", net.JoinHostPort("0.0.0.0", strconv.Itoa(mesh.Port)), "mesh listen address")
		hwaddr     = flag.String("hwaddr", mustHardwareAddr(), "MAC address, i.e. mesh peer name")
		nickname   = flag.String("nickname", mustHostname(), "peer nickname")
		password   = flag.String("password", "", "password (optional)")
		channel    = flag.String("channel", "default", "gossip channel name")
		quicktest  = flag.Int("quicktest", 0, "set to integer 1-9 to enable quick test setup of node")
		n          = flag.Int("n", 3, "number of peers expected (lower bound)")
	)
	flag.Var(peers, "peer", "initial peer (may be repeated)")
	flag.Parse()

	if *quicktest >= 1 && *quicktest <= 9 {
		*hwaddr = fmt.Sprintf("00:00:00:00:00:0%d", *quicktest)
		*meshListen = fmt.Sprintf("0.0.0.0:600%d", *quicktest)
		*apiListen = fmt.Sprintf("0.0.0.0:800%d", *quicktest)
		*nickname = fmt.Sprintf("%d", *quicktest)
		for i := 1; i <= 9; i++ {
			peers.Set(fmt.Sprintf("127.0.0.1:600%d", i))
		}
	}

	logger := log.New(os.Stderr, *nickname+"> ", log.LstdFlags)

	host, portStr, err := net.SplitHostPort(*meshListen)
	if err != nil {
		logger.Fatalf("mesh address: %s: %v", *meshListen, err)
	}
	port, err := strconv.Atoi(portStr)
	if err != nil {
		logger.Fatalf("mesh address: %s: %v", *meshListen, err)
	}

	name, err := mesh.PeerNameFromString(*hwaddr)
	if err != nil {
		logger.Fatalf("%s: %v", *hwaddr, err)
	}

	ln, err := net.Listen("tcp", *apiListen)
	if err != nil {
		logger.Fatal(err)
	}

	logger.Printf("hello!")
	defer logger.Printf("goodbye!")

	// Create, but do not start, a router.
	meshLogger := log.New(ioutil.Discard, "", 0) // no log from mesh please
	router := mesh.NewRouter(mesh.Config{
		Host:               host,
		Port:               port,
		ProtocolMinVersion: mesh.ProtocolMinVersion,
		Password:           []byte(*password),
		ConnLimit:          64,
		PeerDiscovery:      true,
		TrustedSubnets:     []*net.IPNet{},
	}, name, *nickname, mesh.NullOverlay{}, meshLogger)

	// Create a meshconn.Peer.
	peer := meshconn.NewPeer(name, router.Ourself.UID, logger)
	gossip := router.NewGossip(*channel, peer)
	peer.Register(gossip)

	// Start the router and join the mesh.
	func() {
		logger.Printf("mesh router starting (%s)", *meshListen)
		router.Start()
	}()
	defer func() {
		logger.Printf("mesh router stopping")
		router.Stop()
	}()

	router.ConnectionMaker.InitiateConnections(peers.slice(), true)

	terminatec := make(chan struct{})
	terminatedc := make(chan error)
	go func() {
		c := make(chan os.Signal)
		signal.Notify(c, syscall.SIGINT, syscall.SIGTERM)
		sig := <-c                           // receive interrupt
		close(terminatec)                    // terminate metcd.Server
		<-terminatedc                        // wait for shutdown
		terminatedc <- fmt.Errorf("%s", sig) // forward signal
	}()
	go func() {
		metcdServer := metcd.NewServer(router, peer, *n, terminatec, terminatedc, logger)
		grpcServer := metcd.GRPCServer(metcdServer)
		defer grpcServer.Stop()
		logger.Printf("gRPC listening at %s", *apiListen)
		terminatedc <- grpcServer.Serve(ln)
	}()
	logger.Print(<-terminatedc)
	time.Sleep(time.Second) // TODO(pb): there must be a better way
}
コード例 #16
0
ファイル: ring_test.go プロジェクト: duweike/weave
func TestFuzzRing(t *testing.T) {
	var (
		numPeers   = 25
		iterations = 1000
	)

	peers := make([]mesh.PeerName, numPeers)
	for i := 0; i < numPeers; i++ {
		peer, _ := mesh.PeerNameFromString(fmt.Sprintf("%02d:00:00:00:02:00", i))
		peers[i] = peer
	}

	// Make a valid, random ring
	makeGoodRandomRing := func() *Ring {
		addressSpace := end - start
		numTokens := rand.Intn(int(addressSpace))

		tokenMap := make(map[address.Address]bool)
		for i := 0; i < numTokens; i++ {
			tokenMap[address.Address(rand.Intn(int(addressSpace)))] = true
		}
		var tokens []address.Address
		for token := range tokenMap {
			tokens = append(tokens, token)
		}
		sort.Sort(addressSlice(tokens))

		peer := peers[rand.Intn(len(peers))]
		ring := New(start, end, peer)
		for _, token := range tokens {
			peer = peers[rand.Intn(len(peers))]
			ring.Entries = append(ring.Entries, &entry{Token: start + token, Peer: peer})
		}

		ring.assertInvariants()
		return ring
	}

	for i := 0; i < iterations; i++ {
		// make 2 random rings
		ring1 := makeGoodRandomRing()
		ring2 := makeGoodRandomRing()

		// Merge them - this might fail, we don't care
		// We just want to make sure it doesn't panic
		ring1.Merge(*ring2)

		// Check whats left still passes assertions
		ring1.assertInvariants()
		ring2.assertInvariants()
	}

	// Make an invalid, random ring
	makeBadRandomRing := func() *Ring {
		addressSpace := end - start
		numTokens := rand.Intn(int(addressSpace))
		tokens := make([]address.Address, numTokens)
		for i := 0; i < numTokens; i++ {
			tokens[i] = address.Address(rand.Intn(int(addressSpace)))
		}

		peer := peers[rand.Intn(len(peers))]
		ring := New(start, end, peer)
		for _, token := range tokens {
			peer = peers[rand.Intn(len(peers))]
			ring.Entries = append(ring.Entries, &entry{Token: start + token, Peer: peer})
		}

		return ring
	}

	for i := 0; i < iterations; i++ {
		// make 2 random rings
		ring1 := makeGoodRandomRing()
		ring2 := makeBadRandomRing()

		// Merge them - this might fail, we don't care
		// We just want to make sure it doesn't panic
		ring1.Merge(*ring2)

		// Check whats left still passes assertions
		ring1.assertInvariants()
	}
}
コード例 #17
0
ファイル: peer_name_mac_test.go プロジェクト: weaveworks/mesh
func checkFailure(t *testing.T, nameStr string) {
	_, err := mesh.PeerNameFromString(nameStr)
	require.Error(t, err)
}
コード例 #18
0
ファイル: ring_test.go プロジェクト: brb/weave
import (
	"bytes"
	"fmt"
	"math/rand"
	"sort"
	"testing"

	"github.com/stretchr/testify/require"
	"github.com/weaveworks/mesh"

	"github.com/weaveworks/weave/common"
	"github.com/weaveworks/weave/net/address"
)

var (
	peer1name, _ = mesh.PeerNameFromString("01:00:00:00:02:00")
	peer2name, _ = mesh.PeerNameFromString("02:00:00:00:02:00")
	peer3name, _ = mesh.PeerNameFromString("03:00:00:00:02:00")

	start, end    = ParseIP("10.0.0.0"), ParseIP("10.0.0.255")
	dot10, dot245 = ParseIP("10.0.0.10"), ParseIP("10.0.0.245")
	dot250        = ParseIP("10.0.0.250")
	middle        = ParseIP("10.0.0.128")
)

func ParseIP(s string) address.Address {
	addr, _ := address.ParseIP(s)
	return addr
}

func TestInvariants(t *testing.T) {
コード例 #19
0
ファイル: ring_test.go プロジェクト: duweike/weave
func TestFuzzRingHard(t *testing.T) {
	//common.SetLogLevel("debug")
	var (
		numPeers   = 100
		iterations = 3000
		peers      []mesh.PeerName
		rings      []*Ring
		nextPeerID = 0
	)

	addPeer := func() {
		peer, _ := mesh.PeerNameFromString(fmt.Sprintf("%02d:%02d:00:00:00:00", nextPeerID/10, nextPeerID%10))
		common.Log.Debugf("%s: Adding peer", peer)
		nextPeerID++
		peers = append(peers, peer)
		rings = append(rings, New(start, end, peer))
	}

	for i := 0; i < numPeers; i++ {
		addPeer()
	}

	rings[0].ClaimItAll()

	randomPeer := func(exclude int) (int, mesh.PeerName, *Ring) {
		var peerIndex int
		if exclude >= 0 {
			peerIndex = rand.Intn(len(peers) - 1)
			if peerIndex == exclude {
				peerIndex++
			}
		} else {
			peerIndex = rand.Intn(len(peers))
		}
		return peerIndex, peers[peerIndex], rings[peerIndex]
	}

	// Keep a map of index -> ranges, as these are a little expensive to
	// calculate for every ring on every iteration.
	var theRanges = make(map[int][]address.Range)
	theRanges[0] = rings[0].OwnedRanges()

	addOrRmPeer := func() {
		if len(peers) < numPeers {
			addPeer()
			return
		}

		// Pick one peer to remove, and a different one to transfer to
		peerIndex, peername, _ := randomPeer(-1)
		_, otherPeername, otherRing := randomPeer(peerIndex)

		// We need to be in a ~converged ring to rmpeer
		for _, ring := range rings {
			require.NoError(t, otherRing.Merge(*ring))
		}

		common.Log.Debugf("%s: transferring from peer %s", otherPeername, peername)
		otherRing.Transfer(peername, otherPeername)

		// Remove peer from our state
		peers = append(peers[:peerIndex], peers[peerIndex+1:]...)
		rings = append(rings[:peerIndex], rings[peerIndex+1:]...)
		theRanges = make(map[int][]address.Range)

		// And now tell everyone about the transfer - rmpeer is
		// not partition safe
		for i, ring := range rings {
			require.NoError(t, ring.Merge(*otherRing))
			theRanges[i] = ring.OwnedRanges()
		}
	}

	doGrantOrGossip := func() {
		var ringsWithRanges = make([]int, 0, len(rings))
		for index, ranges := range theRanges {
			if len(ranges) > 0 {
				ringsWithRanges = append(ringsWithRanges, index)
			}
		}

		if len(ringsWithRanges) > 0 {
			// Produce a random split in a random owned range, given to a random peer
			indexWithRanges := ringsWithRanges[rand.Intn(len(ringsWithRanges))]
			ownedRanges := theRanges[indexWithRanges]
			ring := rings[indexWithRanges]

			rangeToSplit := ownedRanges[rand.Intn(len(ownedRanges))]
			size := address.Subtract(rangeToSplit.End, rangeToSplit.Start)
			ipInRange := address.Add(rangeToSplit.Start, address.Offset(rand.Intn(int(size))))
			_, peerToGiveTo, _ := randomPeer(-1)
			common.Log.Debugf("%s: Granting [%v, %v) to %s", ring.Peer, ipInRange, rangeToSplit.End, peerToGiveTo)
			ring.GrantRangeToHost(ipInRange, rangeToSplit.End, peerToGiveTo)

			// Now 'gossip' this to a random host (note, note could be same host as above)
			otherIndex, _, otherRing := randomPeer(-1)
			common.Log.Debugf("%s: 'Gossiping' to %s", ring.Peer, otherRing.Peer)
			require.NoError(t, otherRing.Merge(*ring))

			theRanges[indexWithRanges] = ring.OwnedRanges()
			theRanges[otherIndex] = otherRing.OwnedRanges()
			return
		}

		// No rings think they own anything (as gossip might be behind)
		// We're going to pick a random host (which has entries) and gossip
		// it to a random host (which may or may not have entries).
		var ringsWithEntries = make([]*Ring, 0, len(rings))
		for _, ring := range rings {
			if len(ring.Entries) > 0 {
				ringsWithEntries = append(ringsWithEntries, ring)
			}
		}
		ring1 := ringsWithEntries[rand.Intn(len(ringsWithEntries))]
		ring2index, _, ring2 := randomPeer(-1)
		common.Log.Debugf("%s: 'Gossiping' to %s", ring1.Peer, ring2.Peer)
		require.NoError(t, ring2.Merge(*ring1))
		theRanges[ring2index] = ring2.OwnedRanges()
	}

	for i := 0; i < iterations; i++ {
		// about 1 in 10 times, rmpeer or add host
		n := rand.Intn(10)
		switch {
		case n < 1:
			addOrRmPeer()
		default:
			doGrantOrGossip()
		}
	}
}