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

package tests_test

import (
	"flag"
	"strings"
	"time"

	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"

	"github.com/google/goexpect"

	"fmt"

	v12 "k8s.io/api/core/v1"
	v13 "k8s.io/apimachinery/pkg/apis/meta/v1"

	"github.com/onsi/ginkgo/extensions/table"

	"sync"

	"k8s.io/apimachinery/pkg/util/intstr"

	"kubevirt.io/kubevirt/pkg/api/v1"
	"kubevirt.io/kubevirt/pkg/kubecli"
	"kubevirt.io/kubevirt/pkg/log"
	"kubevirt.io/kubevirt/tests"
)

var _ = Describe("Networking", func() {

	flag.Parse()

	virtClient, err := kubecli.GetKubevirtClient()
	tests.PanicOnError(err)

	var inboundVM *v1.VirtualMachine
	var outboundVM *v1.VirtualMachine

	// newHelloWorldJob takes a dns entry or an IP which it will use create a pod
	// which tries to contact the host on port 1500. It expects to receive "Hello World!" to succeed.
	newHelloWorldJob := func(host string) *v12.Pod {
		check := []string{fmt.Sprintf(`set -x; x="$(head -n 1 < <(nc %s 1500 -i 1 -w 1))"; echo "$x" ; if [ "$x" = "Hello World!" ]; then echo "succeeded"; exit 0; else echo "failed"; exit 1; fi`, host)}
		job := tests.RenderJob("netcat", []string{"/bin/bash", "-c"}, check)

		return job
	}

	logPodLogs := func(pod *v12.Pod) {
		defer GinkgoRecover()

		var s int64 = 500
		logs := virtClient.CoreV1().Pods(inboundVM.Namespace).GetLogs(pod.Name, &v12.PodLogOptions{SinceSeconds: &s})
		rawLogs, err := logs.DoRaw()
		Expect(err).ToNot(HaveOccurred())
		log.Log.Infof("%v", rawLogs)
	}

	waitForPodToFinish := func(pod *v12.Pod) v12.PodPhase {
		Eventually(func() v12.PodPhase {
			j, err := virtClient.Core().Pods(inboundVM.ObjectMeta.Namespace).Get(pod.ObjectMeta.Name, v13.GetOptions{})
			Expect(err).ToNot(HaveOccurred())
			return j.Status.Phase
		}, 30*time.Second, 1*time.Second).Should(Or(Equal(v12.PodSucceeded), Equal(v12.PodFailed)))
		j, err := virtClient.Core().Pods(inboundVM.ObjectMeta.Namespace).Get(pod.ObjectMeta.Name, v13.GetOptions{})
		Expect(err).ToNot(HaveOccurred())
		logPodLogs(pod)
		return j.Status.Phase
	}

	// TODO this is not optimal, since the one test which will initiate this, will look slow
	tests.BeforeAll(func() {
		tests.BeforeTestCleanup()

		var wg sync.WaitGroup

		createAndLogin := func(labels map[string]string) (vm *v1.VirtualMachine) {
			vm = tests.NewRandomVMWithEphemeralDiskAndUserdata(tests.RegistryDiskFor(tests.RegistryDiskCirros), "#!/bin/bash\necho 'hello'\n")
			vm.Labels = labels

			// Start VM
			vm, err = virtClient.VM(tests.NamespaceTestDefault).Create(vm)
			Expect(err).ToNot(HaveOccurred())
			tests.WaitForSuccessfulVMStartIgnoreWarnings(vm)

			// Fetch the new VM with updated status
			vm, err = virtClient.VM(tests.NamespaceTestDefault).Get(vm.ObjectMeta.Name, v13.GetOptions{})
			Expect(err).ToNot(HaveOccurred())

			// Lets make sure that the OS is up by waiting until we can login
			expecter, err := tests.LoggedInCirrosExpecter(vm)
			defer expecter.Close()
			Expect(err).ToNot(HaveOccurred())
			return vm
		}
		wg.Add(2)

		// Create inbound VM which listens on port 1500 for incoming connections and repeatedly returns "Hello World!"
		go func() {
			defer wg.Done()
			defer GinkgoRecover()
			inboundVM = createAndLogin(map[string]string{"expose": "me"})
			expecter, _, err := tests.NewConsoleExpecter(virtClient, inboundVM, 10*time.Second)
			defer expecter.Close()
			Expect(err).ToNot(HaveOccurred())
			resp, err := expecter.ExpectBatch([]expect.Batcher{
				&expect.BSnd{S: "\n"},
				&expect.BExp{R: "\\$ "},
				&expect.BSnd{S: "screen -d -m nc -klp 1500 -e echo -e \"Hello World!\"\n"},
				&expect.BExp{R: "\\$ "},
				&expect.BSnd{S: "echo $?\n"},
				&expect.BExp{R: "0"},
			}, 60*time.Second)
			log.DefaultLogger().Infof("%v", resp)
			Expect(err).ToNot(HaveOccurred())
		}()

		// Create a VM and log in, to allow executing arbitrary commands from the terminal
		go func() {
			defer wg.Done()
			defer GinkgoRecover()
			outboundVM = createAndLogin(nil)
		}()

		wg.Wait()
	})

	Context("VirtualMachine attached to the pod network", func() {

		table.DescribeTable("should be able to reach", func(destination string) {
			var cmdCheck, addrShow, addr string

			// assuming pod network is of standard MTU = 1500 (minus 50 bytes for vxlan overhead)
			expectedMtu := 1450
			ipHeaderSize := 28 // IPv4 specific
			payloadSize := expectedMtu - ipHeaderSize

			// Wait until the VM is booted, ping google and check if we can reach the internet
			expecter, _, err := tests.NewConsoleExpecter(virtClient, outboundVM, 10*time.Second)
			defer expecter.Close()
			Expect(err).ToNot(HaveOccurred())

			switch destination {
			case "Internet":
				addr = "www.google.com"
			case "InboundVM":
				addr = inboundVM.Status.Interfaces[0].IP
			}

			// Check br1 MTU inside the pod
			vmPod := tests.GetRunningPodByLabel(outboundVM.Name, v1.DomainLabel, tests.NamespaceTestDefault)
			output, err := tests.ExecuteCommandOnPod(
				virtClient,
				vmPod,
				vmPod.Spec.Containers[0].Name,
				[]string{"ip", "address", "show", "br1"},
			)
			log.Log.Infof("%v", output)
			Expect(err).ToNot(HaveOccurred())
			// the following substring is part of 'ip address show' output
			expectedMtuString := fmt.Sprintf("mtu %d", expectedMtu)
			Expect(strings.Contains(output, expectedMtuString)).To(BeTrue())

			// Check eth0 MTU inside the VM
			addrShow = "ip address show eth0\n"
			out, err := expecter.ExpectBatch([]expect.Batcher{
				&expect.BSnd{S: "\n"},
				&expect.BExp{R: "\\$ "},
				&expect.BSnd{S: addrShow},
				&expect.BExp{R: fmt.Sprintf(".*%s.*\n", expectedMtuString)},
				&expect.BSnd{S: "echo $?\n"},
				&expect.BExp{R: "0"},
			}, 180*time.Second)
			log.Log.Infof("%v", out)
			Expect(err).ToNot(HaveOccurred())

			// Check VM can send MTU sized frames to the VM
			//
			// NOTE: VM is not directly accessible from inside the pod because
			// we transfered its IP address under DHCP server control, so the
			// only thing we can validate is connectivity between VMs
			//
			// NOTE: cirros ping doesn't support -M do that could be used to
			// validate end-to-end connectivity with Don't Fragment flag set
			cmdCheck = fmt.Sprintf("ping %s -c 1 -w 5 -s %d\n", addr, payloadSize)
			out, err = expecter.ExpectBatch([]expect.Batcher{
				&expect.BSnd{S: "\n"},
				&expect.BExp{R: "\\$ "},
				&expect.BSnd{S: cmdCheck},
				&expect.BExp{R: "\\$ "},
				&expect.BSnd{S: "echo $?\n"},
				&expect.BExp{R: "0"},
			}, 180*time.Second)
			log.Log.Infof("%v", out)
			Expect(err).ToNot(HaveOccurred())
		},
			table.Entry("the Inbound VM", "InboundVM"),
			table.Entry("the internet", "Internet"),
		)

		table.DescribeTable("should be reachable via the propagated IP from a Pod", func(op v12.NodeSelectorOperator, hostNetwork bool) {

			ip := inboundVM.Status.Interfaces[0].IP

			//TODO if node count 1, skip whe nv12.NodeSelectorOpOut
			nodes, err := virtClient.CoreV1().Nodes().List(v13.ListOptions{})
			Expect(err).ToNot(HaveOccurred())
			Expect(nodes.Items).ToNot(BeEmpty())
			if len(nodes.Items) == 1 && op == v12.NodeSelectorOpNotIn {
				Skip("Skip network test that requires multiple nodes when only one node is present.")
			}

			// Run netcat and give it one second to ghet "Hello World!" back from the VM
			job := newHelloWorldJob(ip)
			job.Spec.Affinity = &v12.Affinity{
				NodeAffinity: &v12.NodeAffinity{
					RequiredDuringSchedulingIgnoredDuringExecution: &v12.NodeSelector{
						NodeSelectorTerms: []v12.NodeSelectorTerm{
							{
								MatchExpressions: []v12.NodeSelectorRequirement{
									{Key: "kubernetes.io/hostname", Operator: op, Values: []string{inboundVM.Status.NodeName}},
								},
							},
						},
					},
				},
			}
			job.Spec.HostNetwork = hostNetwork

			job, err = virtClient.CoreV1().Pods(inboundVM.ObjectMeta.Namespace).Create(job)
			Expect(err).ToNot(HaveOccurred())
			phase := waitForPodToFinish(job)
			Expect(phase).To(Equal(v12.PodSucceeded))
		},
			table.Entry("on the same node from Pod", v12.NodeSelectorOpIn, false),
			table.Entry("on a different node from Pod", v12.NodeSelectorOpNotIn, false),
			table.Entry("on the same node from Node", v12.NodeSelectorOpIn, true),
			table.Entry("on a different node from Node", v12.NodeSelectorOpNotIn, true),
		)

		Context("with a service matching the vm exposed", func() {
			BeforeEach(func() {
				service := &v12.Service{
					ObjectMeta: v13.ObjectMeta{
						Name: "myservice",
					},
					Spec: v12.ServiceSpec{
						Selector: map[string]string{
							"expose": "me",
						},
						Ports: []v12.ServicePort{
							{Protocol: v12.ProtocolTCP, Port: 1500, TargetPort: intstr.FromInt(1500)},
						},
					},
				}

				_, err := virtClient.CoreV1().Services(inboundVM.Namespace).Create(service)
				Expect(err).ToNot(HaveOccurred())

			})
			It(" should be able to reach the vm based on labels specified on the vm", func() {

				By("starting a pod which tries to reach the vm via the defined service")
				job := newHelloWorldJob(fmt.Sprintf("%s.%s", "myservice", inboundVM.Namespace))
				job, err = virtClient.CoreV1().Pods(inboundVM.Namespace).Create(job)
				Expect(err).ToNot(HaveOccurred())

				By("waiting for the pod to report a successful connection attempt")
				phase := waitForPodToFinish(job)
				Expect(phase).To(Equal(v12.PodSucceeded))
			})
			It("should fail to reach the vm if an invalid servicename is used", func() {

				By("starting a pod which tries to reach the vm via a non-existent service")
				job := newHelloWorldJob(fmt.Sprintf("%s.%s", "wrongservice", inboundVM.Namespace))
				job, err = virtClient.CoreV1().Pods(inboundVM.Namespace).Create(job)
				Expect(err).ToNot(HaveOccurred())
				By("waiting for the pod to report an  unsuccessful connection attempt")
				phase := waitForPodToFinish(job)
				Expect(phase).To(Equal(v12.PodFailed))
			})

			AfterEach(func() {
				Expect(virtClient.CoreV1().Services(inboundVM.Namespace).Delete("myservice", &v13.DeleteOptions{})).To(Succeed())
			})
		})
	})

})
<commit_msg>tests: added STEP descriptions to new MTU related checks<commit_after>/*
 * This file is part of the kubevirt project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * Copyright 2018 Red Hat, Inc.
 *
 */

package tests_test

import (
	"flag"
	"strings"
	"time"

	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"

	"github.com/google/goexpect"

	"fmt"

	v12 "k8s.io/api/core/v1"
	v13 "k8s.io/apimachinery/pkg/apis/meta/v1"

	"github.com/onsi/ginkgo/extensions/table"

	"sync"

	"k8s.io/apimachinery/pkg/util/intstr"

	"kubevirt.io/kubevirt/pkg/api/v1"
	"kubevirt.io/kubevirt/pkg/kubecli"
	"kubevirt.io/kubevirt/pkg/log"
	"kubevirt.io/kubevirt/tests"
)

var _ = Describe("Networking", func() {

	flag.Parse()

	virtClient, err := kubecli.GetKubevirtClient()
	tests.PanicOnError(err)

	var inboundVM *v1.VirtualMachine
	var outboundVM *v1.VirtualMachine

	// newHelloWorldJob takes a dns entry or an IP which it will use create a pod
	// which tries to contact the host on port 1500. It expects to receive "Hello World!" to succeed.
	newHelloWorldJob := func(host string) *v12.Pod {
		check := []string{fmt.Sprintf(`set -x; x="$(head -n 1 < <(nc %s 1500 -i 1 -w 1))"; echo "$x" ; if [ "$x" = "Hello World!" ]; then echo "succeeded"; exit 0; else echo "failed"; exit 1; fi`, host)}
		job := tests.RenderJob("netcat", []string{"/bin/bash", "-c"}, check)

		return job
	}

	logPodLogs := func(pod *v12.Pod) {
		defer GinkgoRecover()

		var s int64 = 500
		logs := virtClient.CoreV1().Pods(inboundVM.Namespace).GetLogs(pod.Name, &v12.PodLogOptions{SinceSeconds: &s})
		rawLogs, err := logs.DoRaw()
		Expect(err).ToNot(HaveOccurred())
		log.Log.Infof("%v", rawLogs)
	}

	waitForPodToFinish := func(pod *v12.Pod) v12.PodPhase {
		Eventually(func() v12.PodPhase {
			j, err := virtClient.Core().Pods(inboundVM.ObjectMeta.Namespace).Get(pod.ObjectMeta.Name, v13.GetOptions{})
			Expect(err).ToNot(HaveOccurred())
			return j.Status.Phase
		}, 30*time.Second, 1*time.Second).Should(Or(Equal(v12.PodSucceeded), Equal(v12.PodFailed)))
		j, err := virtClient.Core().Pods(inboundVM.ObjectMeta.Namespace).Get(pod.ObjectMeta.Name, v13.GetOptions{})
		Expect(err).ToNot(HaveOccurred())
		logPodLogs(pod)
		return j.Status.Phase
	}

	// TODO this is not optimal, since the one test which will initiate this, will look slow
	tests.BeforeAll(func() {
		tests.BeforeTestCleanup()

		var wg sync.WaitGroup

		createAndLogin := func(labels map[string]string) (vm *v1.VirtualMachine) {
			vm = tests.NewRandomVMWithEphemeralDiskAndUserdata(tests.RegistryDiskFor(tests.RegistryDiskCirros), "#!/bin/bash\necho 'hello'\n")
			vm.Labels = labels

			// Start VM
			vm, err = virtClient.VM(tests.NamespaceTestDefault).Create(vm)
			Expect(err).ToNot(HaveOccurred())
			tests.WaitForSuccessfulVMStartIgnoreWarnings(vm)

			// Fetch the new VM with updated status
			vm, err = virtClient.VM(tests.NamespaceTestDefault).Get(vm.ObjectMeta.Name, v13.GetOptions{})
			Expect(err).ToNot(HaveOccurred())

			// Lets make sure that the OS is up by waiting until we can login
			expecter, err := tests.LoggedInCirrosExpecter(vm)
			defer expecter.Close()
			Expect(err).ToNot(HaveOccurred())
			return vm
		}
		wg.Add(2)

		// Create inbound VM which listens on port 1500 for incoming connections and repeatedly returns "Hello World!"
		go func() {
			defer wg.Done()
			defer GinkgoRecover()
			inboundVM = createAndLogin(map[string]string{"expose": "me"})
			expecter, _, err := tests.NewConsoleExpecter(virtClient, inboundVM, 10*time.Second)
			defer expecter.Close()
			Expect(err).ToNot(HaveOccurred())
			resp, err := expecter.ExpectBatch([]expect.Batcher{
				&expect.BSnd{S: "\n"},
				&expect.BExp{R: "\\$ "},
				&expect.BSnd{S: "screen -d -m nc -klp 1500 -e echo -e \"Hello World!\"\n"},
				&expect.BExp{R: "\\$ "},
				&expect.BSnd{S: "echo $?\n"},
				&expect.BExp{R: "0"},
			}, 60*time.Second)
			log.DefaultLogger().Infof("%v", resp)
			Expect(err).ToNot(HaveOccurred())
		}()

		// Create a VM and log in, to allow executing arbitrary commands from the terminal
		go func() {
			defer wg.Done()
			defer GinkgoRecover()
			outboundVM = createAndLogin(nil)
		}()

		wg.Wait()
	})

	Context("VirtualMachine attached to the pod network", func() {

		table.DescribeTable("should be able to reach", func(destination string) {
			var cmdCheck, addrShow, addr string

			// assuming pod network is of standard MTU = 1500 (minus 50 bytes for vxlan overhead)
			expectedMtu := 1450
			ipHeaderSize := 28 // IPv4 specific
			payloadSize := expectedMtu - ipHeaderSize

			// Wait until the VM is booted, ping google and check if we can reach the internet
			expecter, _, err := tests.NewConsoleExpecter(virtClient, outboundVM, 10*time.Second)
			defer expecter.Close()
			Expect(err).ToNot(HaveOccurred())

			switch destination {
			case "Internet":
				addr = "www.google.com"
			case "InboundVM":
				addr = inboundVM.Status.Interfaces[0].IP
			}

			By("checking br1 MTU inside the pod")
			vmPod := tests.GetRunningPodByLabel(outboundVM.Name, v1.DomainLabel, tests.NamespaceTestDefault)
			output, err := tests.ExecuteCommandOnPod(
				virtClient,
				vmPod,
				vmPod.Spec.Containers[0].Name,
				[]string{"ip", "address", "show", "br1"},
			)
			log.Log.Infof("%v", output)
			Expect(err).ToNot(HaveOccurred())
			// the following substring is part of 'ip address show' output
			expectedMtuString := fmt.Sprintf("mtu %d", expectedMtu)
			Expect(strings.Contains(output, expectedMtuString)).To(BeTrue())

			By("checking eth0 MTU inside the VM")
			addrShow = "ip address show eth0\n"
			out, err := expecter.ExpectBatch([]expect.Batcher{
				&expect.BSnd{S: "\n"},
				&expect.BExp{R: "\\$ "},
				&expect.BSnd{S: addrShow},
				&expect.BExp{R: fmt.Sprintf(".*%s.*\n", expectedMtuString)},
				&expect.BSnd{S: "echo $?\n"},
				&expect.BExp{R: "0"},
			}, 180*time.Second)
			log.Log.Infof("%v", out)
			Expect(err).ToNot(HaveOccurred())

			By("checking the VM can send MTU sized frames to another VM")
			// NOTE: VM is not directly accessible from inside the pod because
			// we transfered its IP address under DHCP server control, so the
			// only thing we can validate is connectivity between VMs
			//
			// NOTE: cirros ping doesn't support -M do that could be used to
			// validate end-to-end connectivity with Don't Fragment flag set
			cmdCheck = fmt.Sprintf("ping %s -c 1 -w 5 -s %d\n", addr, payloadSize)
			out, err = expecter.ExpectBatch([]expect.Batcher{
				&expect.BSnd{S: "\n"},
				&expect.BExp{R: "\\$ "},
				&expect.BSnd{S: cmdCheck},
				&expect.BExp{R: "\\$ "},
				&expect.BSnd{S: "echo $?\n"},
				&expect.BExp{R: "0"},
			}, 180*time.Second)
			log.Log.Infof("%v", out)
			Expect(err).ToNot(HaveOccurred())
		},
			table.Entry("the Inbound VM", "InboundVM"),
			table.Entry("the internet", "Internet"),
		)

		table.DescribeTable("should be reachable via the propagated IP from a Pod", func(op v12.NodeSelectorOperator, hostNetwork bool) {

			ip := inboundVM.Status.Interfaces[0].IP

			//TODO if node count 1, skip whe nv12.NodeSelectorOpOut
			nodes, err := virtClient.CoreV1().Nodes().List(v13.ListOptions{})
			Expect(err).ToNot(HaveOccurred())
			Expect(nodes.Items).ToNot(BeEmpty())
			if len(nodes.Items) == 1 && op == v12.NodeSelectorOpNotIn {
				Skip("Skip network test that requires multiple nodes when only one node is present.")
			}

			// Run netcat and give it one second to ghet "Hello World!" back from the VM
			job := newHelloWorldJob(ip)
			job.Spec.Affinity = &v12.Affinity{
				NodeAffinity: &v12.NodeAffinity{
					RequiredDuringSchedulingIgnoredDuringExecution: &v12.NodeSelector{
						NodeSelectorTerms: []v12.NodeSelectorTerm{
							{
								MatchExpressions: []v12.NodeSelectorRequirement{
									{Key: "kubernetes.io/hostname", Operator: op, Values: []string{inboundVM.Status.NodeName}},
								},
							},
						},
					},
				},
			}
			job.Spec.HostNetwork = hostNetwork

			job, err = virtClient.CoreV1().Pods(inboundVM.ObjectMeta.Namespace).Create(job)
			Expect(err).ToNot(HaveOccurred())
			phase := waitForPodToFinish(job)
			Expect(phase).To(Equal(v12.PodSucceeded))
		},
			table.Entry("on the same node from Pod", v12.NodeSelectorOpIn, false),
			table.Entry("on a different node from Pod", v12.NodeSelectorOpNotIn, false),
			table.Entry("on the same node from Node", v12.NodeSelectorOpIn, true),
			table.Entry("on a different node from Node", v12.NodeSelectorOpNotIn, true),
		)

		Context("with a service matching the vm exposed", func() {
			BeforeEach(func() {
				service := &v12.Service{
					ObjectMeta: v13.ObjectMeta{
						Name: "myservice",
					},
					Spec: v12.ServiceSpec{
						Selector: map[string]string{
							"expose": "me",
						},
						Ports: []v12.ServicePort{
							{Protocol: v12.ProtocolTCP, Port: 1500, TargetPort: intstr.FromInt(1500)},
						},
					},
				}

				_, err := virtClient.CoreV1().Services(inboundVM.Namespace).Create(service)
				Expect(err).ToNot(HaveOccurred())

			})
			It(" should be able to reach the vm based on labels specified on the vm", func() {

				By("starting a pod which tries to reach the vm via the defined service")
				job := newHelloWorldJob(fmt.Sprintf("%s.%s", "myservice", inboundVM.Namespace))
				job, err = virtClient.CoreV1().Pods(inboundVM.Namespace).Create(job)
				Expect(err).ToNot(HaveOccurred())

				By("waiting for the pod to report a successful connection attempt")
				phase := waitForPodToFinish(job)
				Expect(phase).To(Equal(v12.PodSucceeded))
			})
			It("should fail to reach the vm if an invalid servicename is used", func() {

				By("starting a pod which tries to reach the vm via a non-existent service")
				job := newHelloWorldJob(fmt.Sprintf("%s.%s", "wrongservice", inboundVM.Namespace))
				job, err = virtClient.CoreV1().Pods(inboundVM.Namespace).Create(job)
				Expect(err).ToNot(HaveOccurred())
				By("waiting for the pod to report an  unsuccessful connection attempt")
				phase := waitForPodToFinish(job)
				Expect(phase).To(Equal(v12.PodFailed))
			})

			AfterEach(func() {
				Expect(virtClient.CoreV1().Services(inboundVM.Namespace).Delete("myservice", &v13.DeleteOptions{})).To(Succeed())
			})
		})
	})

})
<|endoftext|>


<commit_before>// Copyright 2015 Pikkpoiss
//
// 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 main

import (
	"../lib/twodee"
)

type GridRenderer struct {
	sheet  *twodee.Spritesheet
	sprite *twodee.SpriteRenderer
}

func NewGridRenderer(camera *twodee.Camera, sheet *twodee.Spritesheet) (renderer *GridRenderer, err error) {
	var (
		sprite *twodee.SpriteRenderer
	)
	if sprite, err = twodee.NewSpriteRenderer(camera); err != nil {
		return
	}
	renderer = &GridRenderer{
		sprite: sprite,
		sheet:  sheet,
	}
	return
}

func (r *GridRenderer) Delete() {
	r.sprite.Delete()
}

func (r *GridRenderer) Draw(grid *twodee.Grid) {
	var (
		configs = []twodee.SpriteConfig{}
		x       int32
		y       int32
	)
	for x = 0; x < grid.Width; x++ {
		for y = 0; y < grid.Height; y++ {
			configs = append(configs, r.spriteConfig(r.sheet, int(x), int(y)))
		}
	}
	r.sprite.Draw(configs)
}

func (r *GridRenderer) spriteConfig(sheet *twodee.Spritesheet, x, y int) twodee.SpriteConfig {
	frame := sheet.GetFrame("numbered_squares_00")
	return twodee.SpriteConfig{
		View: twodee.ModelViewConfig{
			float32(x), float32(y), 0,
			0, 0, 0,
			1.0, 1.0, 1.0,
		},
		Frame: frame.Frame,
	}
}
<commit_msg>Offset by half<commit_after>// Copyright 2015 Pikkpoiss
//
// 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 main

import (
	"../lib/twodee"
)

type GridRenderer struct {
	sheet  *twodee.Spritesheet
	sprite *twodee.SpriteRenderer
}

func NewGridRenderer(camera *twodee.Camera, sheet *twodee.Spritesheet) (renderer *GridRenderer, err error) {
	var (
		sprite *twodee.SpriteRenderer
	)
	if sprite, err = twodee.NewSpriteRenderer(camera); err != nil {
		return
	}
	renderer = &GridRenderer{
		sprite: sprite,
		sheet:  sheet,
	}
	return
}

func (r *GridRenderer) Delete() {
	r.sprite.Delete()
}

func (r *GridRenderer) Draw(grid *twodee.Grid) {
	var (
		configs = []twodee.SpriteConfig{}
		x       int32
		y       int32
	)
	for x = 0; x < grid.Width; x++ {
		for y = 0; y < grid.Height; y++ {
			configs = append(configs, r.spriteConfig(r.sheet, int(x), int(y)))
		}
	}
	r.sprite.Draw(configs)
}

func (r *GridRenderer) spriteConfig(sheet *twodee.Spritesheet, x, y int) twodee.SpriteConfig {
	frame := sheet.GetFrame("numbered_squares_00")
	return twodee.SpriteConfig{
		View: twodee.ModelViewConfig{
			float32(x) + 0.5, float32(y) + 0.5, 0,
			0, 0, 0,
			1.0, 1.0, 1.0,
		},
		Frame: frame.Frame,
	}
}
<|endoftext|>


<commit_before>package collector

import (
	"context"
	"encoding/json"
	"fmt"
	"io"
	"log"
	"net/http"
	"os"
	"strings"
	"sync"
	"time"

	grpc_prometheus "github.com/grpc-ecosystem/go-grpc-prometheus"
	"github.com/karimra/gnmic/outputs"
	"github.com/openconfig/gnmi/proto/gnmi"
	"github.com/prometheus/client_golang/prometheus"
	"github.com/prometheus/client_golang/prometheus/promhttp"
	"google.golang.org/grpc"
)

// Config is the collector config
type Config struct {
	PrometheusAddress string
	Debug             bool
}

// Collector //
type Collector struct {
	Config        *Config
	Subscriptions map[string]*SubscriptionConfig
	Outputs       map[string][]outputs.Output
	DialOpts      []grpc.DialOption
	//
	m             *sync.Mutex
	Targets       map[string]*Target
	Logger        *log.Logger
	httpServer    *http.Server
	defaultOutput io.Writer
	ctx           context.Context
	cancelFn      context.CancelFunc
}

// NewCollector //
func NewCollector(ctx context.Context,
	config *Config,
	targetConfigs map[string]*TargetConfig,
	subscriptions map[string]*SubscriptionConfig,
	outputs map[string][]outputs.Output,
	dialOpts []grpc.DialOption,
	logger *log.Logger,
) *Collector {
	nctx, cancel := context.WithCancel(ctx)
	grpcMetrics := grpc_prometheus.NewClientMetrics()
	reg := prometheus.NewRegistry()
	reg.MustRegister(prometheus.NewGoCollector())
	grpcMetrics.EnableClientHandlingTimeHistogram()
	reg.MustRegister(grpcMetrics)
	httpServer := &http.Server{
		Handler: promhttp.HandlerFor(reg, promhttp.HandlerOpts{}),
		Addr:    config.PrometheusAddress,
	}
	c := &Collector{
		Config:        config,
		Subscriptions: subscriptions,
		Outputs:       outputs,
		DialOpts:      dialOpts,
		m:             new(sync.Mutex),
		Targets:       make(map[string]*Target),
		Logger:        logger,
		httpServer:    httpServer,
		defaultOutput: os.Stdout,
		ctx:           nctx,
		cancelFn:      cancel,
	}
	wg := new(sync.WaitGroup)
	wg.Add(len(targetConfigs))
	for _, tc := range targetConfigs {
		go func(tc *TargetConfig) {
			defer wg.Done()
			err := c.InitTarget(tc)
			if err != nil {
				c.Logger.Printf("failed to initialize target '%s': %v", tc.Name, err)
				return
			}
			c.Logger.Printf("target '%s' initialized", tc.Name)
		}(tc)
	}
	wg.Wait()
	return c
}

// InitTarget initializes a target based on *TargetConfig
func (c *Collector) InitTarget(tc *TargetConfig) error {
	t := NewTarget(tc)
	//
	t.Subscriptions = make([]*SubscriptionConfig, 0, len(tc.Subscriptions))
	for _, subName := range tc.Subscriptions {
		if sub, ok := c.Subscriptions[subName]; ok {
			t.Subscriptions = append(t.Subscriptions, sub)
		}
	}
	if len(t.Subscriptions) == 0 {
		t.Subscriptions = make([]*SubscriptionConfig, 0, len(c.Subscriptions))
		for _, sub := range c.Subscriptions {
			t.Subscriptions = append(t.Subscriptions, sub)
		}
	}
	//
	t.Outputs = make([]outputs.Output, 0, len(tc.Outputs))
	for _, outName := range tc.Outputs {
		if outs, ok := c.Outputs[outName]; ok {
			for _, o := range outs {
				t.Outputs = append(t.Outputs, o)
			}
		}
	}
	if len(t.Outputs) == 0 {
		t.Outputs = make([]outputs.Output, 0, len(c.Outputs))
		for _, o := range c.Outputs {
			t.Outputs = append(t.Outputs, o...)
		}
	}
	//
	err := t.CreateGNMIClient(c.ctx, c.DialOpts...)
	if err != nil {
		return err
	}
	t.ctx, t.cancelFn = context.WithCancel(c.ctx)
	c.m.Lock()
	defer c.m.Unlock()
	c.Targets[t.Config.Name] = t
	return nil
}

// Subscribe //
func (c *Collector) Subscribe(tName string) error {
	if t, ok := c.Targets[tName]; ok {
		for _, sc := range t.Subscriptions {
			req, err := sc.CreateSubscribeRequest()
			if err != nil {
				return err
			}
			go t.Subscribe(c.ctx, req, sc.Name)
		}
		return nil
	}
	return fmt.Errorf("unknown target name: %s", tName)
}

// Start start the prometheus server as well as a goroutine per target selecting on the response chan, the error chan and the ctx.Done() chan
func (c *Collector) Start() {
	go func() {
		if err := c.httpServer.ListenAndServe(); err != nil {
			c.Logger.Printf("Unable to start prometheus http server: %v", err)
			return
		}
	}()
	wg := new(sync.WaitGroup)
	wg.Add(len(c.Targets))
	for _, t := range c.Targets {
		go func(t *Target) {
			defer wg.Done()
			for {
				select {
				case rsp := <-t.SubscribeResponses:
					m := make(map[string]interface{})
					m["subscription-name"] = rsp.SubscriptionName
					b, err := c.FormatMsg(m, rsp.Response)
					if err != nil {
						c.Logger.Printf("failed formatting msg from target '%s': %v", t.Config.Name, err)
						continue
					}
					go t.Export(b, outputs.Meta{"source": t.Config.Name})
				case err := <-t.Errors:
					c.Logger.Printf("target '%s' error: %v", t.Config.Name, err)
				case <-t.ctx.Done():
					return
				}
			}
		}(t)
	}
	wg.Wait()
}

// FormatMsg formats the gnmi.SubscribeResponse and returns a []byte and an error
func (c *Collector) FormatMsg(meta map[string]interface{}, rsp *gnmi.SubscribeResponse) ([]byte, error) {
	switch rsp := rsp.Response.(type) {
	case *gnmi.SubscribeResponse_Update:
		msg := new(msg)
		msg.Timestamp = rsp.Update.Timestamp
		t := time.Unix(0, rsp.Update.Timestamp)
		msg.Time = &t
		if meta == nil {
			meta = make(map[string]interface{})
		}
		msg.Prefix = gnmiPathToXPath(rsp.Update.Prefix)
		var ok bool
		if _, ok = meta["source"]; ok {
			msg.Source = fmt.Sprintf("%s", meta["source"])
		}
		if _, ok = meta["system-name"]; ok {
			msg.SystemName = fmt.Sprintf("%s", meta["system-name"])
		}
		if _, ok = meta["subscription-name"]; ok {
			msg.SubscriptionName = fmt.Sprintf("%s", meta["subscription-name"])
		}
		for i, upd := range rsp.Update.Update {
			pathElems := make([]string, 0, len(upd.Path.Elem))
			for _, pElem := range upd.Path.Elem {
				pathElems = append(pathElems, pElem.GetName())
			}
			value, err := getValue(upd.Val)
			if err != nil {
				c.Logger.Println(err)
			}
			msg.Updates = append(msg.Updates,
				&update{
					Path:   gnmiPathToXPath(upd.Path),
					Values: make(map[string]interface{}),
				})
			msg.Updates[i].Values[strings.Join(pathElems, "/")] = value
		}
		for _, del := range rsp.Update.Delete {
			msg.Deletes = append(msg.Deletes, gnmiPathToXPath(del))
		}
		data, err := json.Marshal(msg)
		if err != nil {
			return nil, err
		}
		return data, nil
	}
	return nil, nil
}

// TargetPoll sends a gnmi.SubscribeRequest_Poll to targetName and returns the response and an error,
// it uses the targetName and the subscriptionName strings to find the gnmi.GNMI_SubscribeClient
func (c *Collector) TargetPoll(targetName, subscriptionName string) (*gnmi.SubscribeResponse, error) {
	if sub, ok := c.Subscriptions[subscriptionName]; ok {
		if strings.ToUpper(sub.Mode) != "POLL" {
			return nil, fmt.Errorf("subscription '%s' is not a POLL subscription", subscriptionName)
		}
		if t, ok := c.Targets[targetName]; ok {
			if subClient, ok := t.SubscribeClients[subscriptionName]; ok {
				err := subClient.Send(&gnmi.SubscribeRequest{
					Request: &gnmi.SubscribeRequest_Poll{
						Poll: &gnmi.Poll{},
					},
				})
				if err != nil {
					return nil, err
				}
				subscribeRsp, err := subClient.Recv()
				if err != nil {
					return nil, err
				}
				return subscribeRsp, nil
			}
		}
		return nil, fmt.Errorf("unknown target name '%s'", targetName)
	}
	return nil, fmt.Errorf("unknown subscription name '%s'", subscriptionName)
}

// PolledSubscriptionsTargets returns a map of target name to a list of subscription names that have Mode == POLL
func (c *Collector) PolledSubscriptionsTargets() map[string][]string {
	result := make(map[string][]string)
	for tn, target := range c.Targets {
		for _, sub := range target.Subscriptions {
			if strings.ToUpper(sub.Mode) == "POLL" {
				if result[tn] == nil {
					result[tn] = make([]string, 0)
				}
				result[tn] = append(result[tn], sub.Name)
			}
		}
	}
	return result
}
<commit_msg>add source to msg format meta<commit_after>package collector

import (
	"context"
	"encoding/json"
	"fmt"
	"io"
	"log"
	"net/http"
	"os"
	"strings"
	"sync"
	"time"

	grpc_prometheus "github.com/grpc-ecosystem/go-grpc-prometheus"
	"github.com/karimra/gnmic/outputs"
	"github.com/openconfig/gnmi/proto/gnmi"
	"github.com/prometheus/client_golang/prometheus"
	"github.com/prometheus/client_golang/prometheus/promhttp"
	"google.golang.org/grpc"
)

// Config is the collector config
type Config struct {
	PrometheusAddress string
	Debug             bool
}

// Collector //
type Collector struct {
	Config        *Config
	Subscriptions map[string]*SubscriptionConfig
	Outputs       map[string][]outputs.Output
	DialOpts      []grpc.DialOption
	//
	m             *sync.Mutex
	Targets       map[string]*Target
	Logger        *log.Logger
	httpServer    *http.Server
	defaultOutput io.Writer
	ctx           context.Context
	cancelFn      context.CancelFunc
}

// NewCollector //
func NewCollector(ctx context.Context,
	config *Config,
	targetConfigs map[string]*TargetConfig,
	subscriptions map[string]*SubscriptionConfig,
	outputs map[string][]outputs.Output,
	dialOpts []grpc.DialOption,
	logger *log.Logger,
) *Collector {
	nctx, cancel := context.WithCancel(ctx)
	grpcMetrics := grpc_prometheus.NewClientMetrics()
	reg := prometheus.NewRegistry()
	reg.MustRegister(prometheus.NewGoCollector())
	grpcMetrics.EnableClientHandlingTimeHistogram()
	reg.MustRegister(grpcMetrics)
	httpServer := &http.Server{
		Handler: promhttp.HandlerFor(reg, promhttp.HandlerOpts{}),
		Addr:    config.PrometheusAddress,
	}
	c := &Collector{
		Config:        config,
		Subscriptions: subscriptions,
		Outputs:       outputs,
		DialOpts:      dialOpts,
		m:             new(sync.Mutex),
		Targets:       make(map[string]*Target),
		Logger:        logger,
		httpServer:    httpServer,
		defaultOutput: os.Stdout,
		ctx:           nctx,
		cancelFn:      cancel,
	}
	wg := new(sync.WaitGroup)
	wg.Add(len(targetConfigs))
	for _, tc := range targetConfigs {
		go func(tc *TargetConfig) {
			defer wg.Done()
			err := c.InitTarget(tc)
			if err != nil {
				c.Logger.Printf("failed to initialize target '%s': %v", tc.Name, err)
				return
			}
			c.Logger.Printf("target '%s' initialized", tc.Name)
		}(tc)
	}
	wg.Wait()
	return c
}

// InitTarget initializes a target based on *TargetConfig
func (c *Collector) InitTarget(tc *TargetConfig) error {
	t := NewTarget(tc)
	//
	t.Subscriptions = make([]*SubscriptionConfig, 0, len(tc.Subscriptions))
	for _, subName := range tc.Subscriptions {
		if sub, ok := c.Subscriptions[subName]; ok {
			t.Subscriptions = append(t.Subscriptions, sub)
		}
	}
	if len(t.Subscriptions) == 0 {
		t.Subscriptions = make([]*SubscriptionConfig, 0, len(c.Subscriptions))
		for _, sub := range c.Subscriptions {
			t.Subscriptions = append(t.Subscriptions, sub)
		}
	}
	//
	t.Outputs = make([]outputs.Output, 0, len(tc.Outputs))
	for _, outName := range tc.Outputs {
		if outs, ok := c.Outputs[outName]; ok {
			for _, o := range outs {
				t.Outputs = append(t.Outputs, o)
			}
		}
	}
	if len(t.Outputs) == 0 {
		t.Outputs = make([]outputs.Output, 0, len(c.Outputs))
		for _, o := range c.Outputs {
			t.Outputs = append(t.Outputs, o...)
		}
	}
	//
	err := t.CreateGNMIClient(c.ctx, c.DialOpts...)
	if err != nil {
		return err
	}
	t.ctx, t.cancelFn = context.WithCancel(c.ctx)
	c.m.Lock()
	defer c.m.Unlock()
	c.Targets[t.Config.Name] = t
	return nil
}

// Subscribe //
func (c *Collector) Subscribe(tName string) error {
	if t, ok := c.Targets[tName]; ok {
		for _, sc := range t.Subscriptions {
			req, err := sc.CreateSubscribeRequest()
			if err != nil {
				return err
			}
			go t.Subscribe(c.ctx, req, sc.Name)
		}
		return nil
	}
	return fmt.Errorf("unknown target name: %s", tName)
}

// Start start the prometheus server as well as a goroutine per target selecting on the response chan, the error chan and the ctx.Done() chan
func (c *Collector) Start() {
	go func() {
		if err := c.httpServer.ListenAndServe(); err != nil {
			c.Logger.Printf("Unable to start prometheus http server: %v", err)
			return
		}
	}()
	wg := new(sync.WaitGroup)
	wg.Add(len(c.Targets))
	for _, t := range c.Targets {
		go func(t *Target) {
			defer wg.Done()
			for {
				select {
				case rsp := <-t.SubscribeResponses:
					m := make(map[string]interface{})
					m["subscription-name"] = rsp.SubscriptionName
					m["source"] = t.Config.Name
					b, err := c.FormatMsg(m, rsp.Response)
					if err != nil {
						c.Logger.Printf("failed formatting msg from target '%s': %v", t.Config.Name, err)
						continue
					}
					go t.Export(b, outputs.Meta{"source": t.Config.Name})
				case err := <-t.Errors:
					c.Logger.Printf("target '%s' error: %v", t.Config.Name, err)
				case <-t.ctx.Done():
					return
				}
			}
		}(t)
	}
	wg.Wait()
}

// FormatMsg formats the gnmi.SubscribeResponse and returns a []byte and an error
func (c *Collector) FormatMsg(meta map[string]interface{}, rsp *gnmi.SubscribeResponse) ([]byte, error) {
	switch rsp := rsp.Response.(type) {
	case *gnmi.SubscribeResponse_Update:
		msg := new(msg)
		msg.Timestamp = rsp.Update.Timestamp
		t := time.Unix(0, rsp.Update.Timestamp)
		msg.Time = &t
		if meta == nil {
			meta = make(map[string]interface{})
		}
		msg.Prefix = gnmiPathToXPath(rsp.Update.Prefix)
		var ok bool
		if _, ok = meta["source"]; ok {
			msg.Source = fmt.Sprintf("%s", meta["source"])
		}
		if _, ok = meta["system-name"]; ok {
			msg.SystemName = fmt.Sprintf("%s", meta["system-name"])
		}
		if _, ok = meta["subscription-name"]; ok {
			msg.SubscriptionName = fmt.Sprintf("%s", meta["subscription-name"])
		}
		for i, upd := range rsp.Update.Update {
			pathElems := make([]string, 0, len(upd.Path.Elem))
			for _, pElem := range upd.Path.Elem {
				pathElems = append(pathElems, pElem.GetName())
			}
			value, err := getValue(upd.Val)
			if err != nil {
				c.Logger.Println(err)
			}
			msg.Updates = append(msg.Updates,
				&update{
					Path:   gnmiPathToXPath(upd.Path),
					Values: make(map[string]interface{}),
				})
			msg.Updates[i].Values[strings.Join(pathElems, "/")] = value
		}
		for _, del := range rsp.Update.Delete {
			msg.Deletes = append(msg.Deletes, gnmiPathToXPath(del))
		}
		data, err := json.Marshal(msg)
		if err != nil {
			return nil, err
		}
		return data, nil
	}
	return nil, nil
}

// TargetPoll sends a gnmi.SubscribeRequest_Poll to targetName and returns the response and an error,
// it uses the targetName and the subscriptionName strings to find the gnmi.GNMI_SubscribeClient
func (c *Collector) TargetPoll(targetName, subscriptionName string) (*gnmi.SubscribeResponse, error) {
	if sub, ok := c.Subscriptions[subscriptionName]; ok {
		if strings.ToUpper(sub.Mode) != "POLL" {
			return nil, fmt.Errorf("subscription '%s' is not a POLL subscription", subscriptionName)
		}
		if t, ok := c.Targets[targetName]; ok {
			if subClient, ok := t.SubscribeClients[subscriptionName]; ok {
				err := subClient.Send(&gnmi.SubscribeRequest{
					Request: &gnmi.SubscribeRequest_Poll{
						Poll: &gnmi.Poll{},
					},
				})
				if err != nil {
					return nil, err
				}
				subscribeRsp, err := subClient.Recv()
				if err != nil {
					return nil, err
				}
				return subscribeRsp, nil
			}
		}
		return nil, fmt.Errorf("unknown target name '%s'", targetName)
	}
	return nil, fmt.Errorf("unknown subscription name '%s'", subscriptionName)
}

// PolledSubscriptionsTargets returns a map of target name to a list of subscription names that have Mode == POLL
func (c *Collector) PolledSubscriptionsTargets() map[string][]string {
	result := make(map[string][]string)
	for tn, target := range c.Targets {
		for _, sub := range target.Subscriptions {
			if strings.ToUpper(sub.Mode) == "POLL" {
				if result[tn] == nil {
					result[tn] = make([]string, 0)
				}
				result[tn] = append(result[tn], sub.Name)
			}
		}
	}
	return result
}
<|endoftext|>


<commit_before>package collector

import (
	"bytes"
	"database/sql"
	"regexp"
	"strconv"

	"github.com/prometheus/client_golang/prometheus"
)

const (
	// Exporter namespace.
	namespace = "mysql"
	// Math constant for picoseconds to seconds.
	picoSeconds = 1e12
	// Query to check whether user/table/client stats are enabled.
	userstatCheckQuery = `SHOW VARIABLES WHERE Variable_Name='userstat'
		OR Variable_Name='userstat_running'`
)

var logRE = regexp.MustCompile(`.+\.(\d+)$`)

func newDesc(subsystem, name, help string) *prometheus.Desc {
	return prometheus.NewDesc(
		prometheus.BuildFQName(namespace, subsystem, name),
		help, nil, nil,
	)
}

func parseStatus(data sql.RawBytes) (float64, bool) {
	if bytes.Compare(data, []byte("Yes")) == 0 || bytes.Compare(data, []byte("ON")) == 0 {
		return 1, true
	}
	if bytes.Compare(data, []byte("No")) == 0 || bytes.Compare(data, []byte("OFF")) == 0 {
		return 0, true
	}
	// SHOW SLAVE STATUS Slave_IO_Running can return "Connecting" which is a non-running state.
	if bytes.Compare(data, []byte("Connecting")) == 0 {
		return 0, true
	}
	// SHOW GLOBAL STATUS like 'wsrep_cluster_status' can return "Primary" or "Non-Primary"/"Disconnected"
	if bytes.Compare(data, []byte("Primary")) == 0 {
		return 1, true
	}
	if bytes.Compare(data, []byte("Non-Primary")) == 0 || bytes.Compare(data, []byte("Disconnected")) == 0 {
		return 0, true
	}
	if logNum := logRE.Find(data); logNum != nil {
		value, err := strconv.ParseFloat(string(logNum), 64)
		return value, err == nil
	}
	value, err := strconv.ParseFloat(string(data), 64)
	return value, err == nil
}
<commit_msg>Use GLOBAL to prevent mysql deadlock (#336)<commit_after>package collector

import (
	"bytes"
	"database/sql"
	"regexp"
	"strconv"

	"github.com/prometheus/client_golang/prometheus"
)

const (
	// Exporter namespace.
	namespace = "mysql"
	// Math constant for picoseconds to seconds.
	picoSeconds = 1e12
	// Query to check whether user/table/client stats are enabled.
	userstatCheckQuery = `SHOW GLOBAL VARIABLES WHERE Variable_Name='userstat'
		OR Variable_Name='userstat_running'`
)

var logRE = regexp.MustCompile(`.+\.(\d+)$`)

func newDesc(subsystem, name, help string) *prometheus.Desc {
	return prometheus.NewDesc(
		prometheus.BuildFQName(namespace, subsystem, name),
		help, nil, nil,
	)
}

func parseStatus(data sql.RawBytes) (float64, bool) {
	if bytes.Compare(data, []byte("Yes")) == 0 || bytes.Compare(data, []byte("ON")) == 0 {
		return 1, true
	}
	if bytes.Compare(data, []byte("No")) == 0 || bytes.Compare(data, []byte("OFF")) == 0 {
		return 0, true
	}
	// SHOW SLAVE STATUS Slave_IO_Running can return "Connecting" which is a non-running state.
	if bytes.Compare(data, []byte("Connecting")) == 0 {
		return 0, true
	}
	// SHOW GLOBAL STATUS like 'wsrep_cluster_status' can return "Primary" or "Non-Primary"/"Disconnected"
	if bytes.Compare(data, []byte("Primary")) == 0 {
		return 1, true
	}
	if bytes.Compare(data, []byte("Non-Primary")) == 0 || bytes.Compare(data, []byte("Disconnected")) == 0 {
		return 0, true
	}
	if logNum := logRE.Find(data); logNum != nil {
		value, err := strconv.ParseFloat(string(logNum), 64)
		return value, err == nil
	}
	value, err := strconv.ParseFloat(string(data), 64)
	return value, err == nil
}
<|endoftext|>


<commit_before>package log_test

import (
	"testing"

	"v2ray.com/core/common/log"
	"v2ray.com/core/common/net"
	. "v2ray.com/ext/assert"
)

type testLogger struct {
	value string
}

func (l *testLogger) Handle(msg log.Message) {
	l.value = msg.String()
}

func TestLogRecord(t *testing.T) {
	assert := With(t)

	var logger testLogger
	log.RegisterHandler(&logger)

	ip := "8.8.8.8"
	log.Record(&log.GeneralMessage{
		Severity: log.Severity_Error,
		Content:  net.ParseAddress(ip),
	})

	assert(logger.value, Equals, "[Error]: "+ip)
}
<commit_msg>fix log test<commit_after>package log_test

import (
	"testing"

	"v2ray.com/core/common/log"
	"v2ray.com/core/common/net"
	. "v2ray.com/ext/assert"
)

type testLogger struct {
	value string
}

func (l *testLogger) Handle(msg log.Message) {
	l.value = msg.String()
}

func TestLogRecord(t *testing.T) {
	assert := With(t)

	var logger testLogger
	log.RegisterHandler(&logger)

	ip := "8.8.8.8"
	log.Record(&log.GeneralMessage{
		Severity: log.Severity_Error,
		Content:  net.ParseAddress(ip),
	})

	assert(logger.value, Equals, "[Error] "+ip)
}
<|endoftext|>


<commit_before>package launcher

import (
	"fmt"
	"os"
	"os/exec"

	"path"

	"github.com/ncodes/cocoon/core/others"
	logging "github.com/op/go-logging"
)

var log = logging.MustGetLogger("launcher")

// Launcher defines cocoon code
// deployment service.
type Launcher struct {
	failed    chan bool
	languages []Language
}

// NewLauncher creates a new launcher
func NewLauncher(failed chan bool) *Launcher {
	return &Launcher{
		failed: failed,
	}
}

// failed sends true or false to the
// launch failed channel to indicate success or failure
func (lc *Launcher) setFailed(v bool) {
	lc.failed <- v
}

// Launch starts a cocoon code
func (lc *Launcher) Launch(req *Request) {

	log.Info("Ready to install cocoon code")
	log.Debugf("Found ccode url=%s and lang=%s", req.URL, req.Lang)

	lang := lc.GetLanguage(req.Lang)
	if lang == nil {
		log.Errorf("cocoon code language (%s) not supported", req.Lang)
		lc.setFailed(true)
		return
	}

	_, err := lc.fetchSource(req, lang)
	if err != nil {
		log.Error(err.Error())
		lc.setFailed(true)
		return
	}

	log.Info(lang)
}

// AddLanguage adds a new langauge to the launcher.
// Will return error if language is already added
func (lc *Launcher) AddLanguage(lang Language) error {
	if lc.GetLanguage(lang.GetName()) != nil {
		return fmt.Errorf("language already exist")
	}
	lc.languages = append(lc.languages, lang)
	return nil
}

// GetLanguage will return a langauges or nil if not found
func (lc *Launcher) GetLanguage(name string) Language {
	for _, l := range lc.languages {
		if l.GetName() == name {
			return l
		}
	}
	return nil
}

// GetLanguages returns all languages added to the launcher
func (lc *Launcher) GetLanguages() []Language {
	return lc.languages
}

// fetchSource fetches the cocoon code source from
// a remote address
func (lc *Launcher) fetchSource(req *Request, lang Language) (string, error) {

	if !others.IsGithubRepoURL(req.URL) {
		return "", fmt.Errorf("only public source code hosted on github is supported") // TODO: support zip files
	}

	lc.fetchFromGit(req, lang)

	return "", nil
}

// findLaunch looks for a previous stored launch/Redeployment by id
// TODO: needs implementation
func (lc *Launcher) findLaunch(id string) interface{} {
	return nil
}

// fetchFromGit fetchs cocoon code from git repo.
// and returns the download directory.
func (lc *Launcher) fetchFromGit(req *Request, lang Language) (string, error) {

	var repoTarURL, downloadDst, unpackDst string
	var err error

	// checks if job was previously deployed. find a job by the job name.
	if lc.findLaunch(req.ID) != nil {
		return "", fmt.Errorf("cocoon code was previously launched") // TODO: fetch last launch tag and use it
	}

	repoTarURL, err = others.GetGithubRepoRelease(req.URL, req.Tag)
	if err != nil {
		return "", fmt.Errorf("Failed to fetch release from github repo. %s", err)
	}

	// set tag to latest if not provided
	tagStr := req.Tag
	if tagStr == "" {
		tagStr = "latest"
	}

	// determine download directory
	downloadDst = lang.GetDownloadDestination(req.URL)
	unpackDst = downloadDst

	// delete download directory if it exists
	if _, err := os.Stat(downloadDst); err == nil {
		// log.Info("Download destination is not empty. Deleting content")
		// if err = os.RemoveAll(downloadDst); err != nil {
		// 	return "", fmt.Errorf("failed to delete contents of download directory")
		// }
		// log.Info("Download directory has been deleted")
	}

	log.Info("Downloading cocoon repository with tag=%s, dst=%s", tagStr, downloadDst)
	filePath := path.Join(downloadDst, fmt.Sprintf("%s.tar.gz", req.ID))
	err = others.DownloadFile(repoTarURL, filePath, func(buf []byte) {})
	if err != nil {
		return "", err
	}

	log.Info("Successfully downloaded cocoon code")
	log.Debugf("Unpacking cocoon code to %s", filePath)

	if err = os.MkdirAll(unpackDst, os.ModePerm); err != nil {
		return "", fmt.Errorf("Failed to create unpack directory. %s", err)
	}

	// unpack tarball
	cmd := "tar"
	args := []string{"-xf", filePath, "-C", unpackDst, "--strip-components", "1"}
	if err = exec.Command(cmd, args...).Run(); err != nil {
		return "", fmt.Errorf("Failed to unpack cocoon code repo tarball. %s", err)
	}

	log.Infof("Successfully unpacked cocoon code to %s", unpackDst)

	os.Remove(filePath)
	log.Info("Deleted the cocoon code tarball")

	return unpackDst, nil
}
<commit_msg>creates downlaod directory and remove the mention of an unpack directory it is the same with the download directory<commit_after>package launcher

import (
	"fmt"
	"os"
	"os/exec"

	"path"

	"github.com/ncodes/cocoon/core/others"
	logging "github.com/op/go-logging"
)

var log = logging.MustGetLogger("launcher")

// Launcher defines cocoon code
// deployment service.
type Launcher struct {
	failed    chan bool
	languages []Language
}

// NewLauncher creates a new launcher
func NewLauncher(failed chan bool) *Launcher {
	return &Launcher{
		failed: failed,
	}
}

// failed sends true or false to the
// launch failed channel to indicate success or failure
func (lc *Launcher) setFailed(v bool) {
	lc.failed <- v
}

// Launch starts a cocoon code
func (lc *Launcher) Launch(req *Request) {

	log.Info("Ready to install cocoon code")
	log.Debugf("Found ccode url=%s and lang=%s", req.URL, req.Lang)

	lang := lc.GetLanguage(req.Lang)
	if lang == nil {
		log.Errorf("cocoon code language (%s) not supported", req.Lang)
		lc.setFailed(true)
		return
	}

	_, err := lc.fetchSource(req, lang)
	if err != nil {
		log.Error(err.Error())
		lc.setFailed(true)
		return
	}

	log.Info(lang)
}

// AddLanguage adds a new langauge to the launcher.
// Will return error if language is already added
func (lc *Launcher) AddLanguage(lang Language) error {
	if lc.GetLanguage(lang.GetName()) != nil {
		return fmt.Errorf("language already exist")
	}
	lc.languages = append(lc.languages, lang)
	return nil
}

// GetLanguage will return a langauges or nil if not found
func (lc *Launcher) GetLanguage(name string) Language {
	for _, l := range lc.languages {
		if l.GetName() == name {
			return l
		}
	}
	return nil
}

// GetLanguages returns all languages added to the launcher
func (lc *Launcher) GetLanguages() []Language {
	return lc.languages
}

// fetchSource fetches the cocoon code source from
// a remote address
func (lc *Launcher) fetchSource(req *Request, lang Language) (string, error) {

	if !others.IsGithubRepoURL(req.URL) {
		return "", fmt.Errorf("only public source code hosted on github is supported") // TODO: support zip files
	}

	return lc.fetchFromGit(req, lang)
}

// findLaunch looks for a previous stored launch/Redeployment by id
// TODO: needs implementation
func (lc *Launcher) findLaunch(id string) interface{} {
	return nil
}

// fetchFromGit fetchs cocoon code from git repo.
// and returns the download directory.
func (lc *Launcher) fetchFromGit(req *Request, lang Language) (string, error) {

	var repoTarURL, downloadDst string
	var err error

	// checks if job was previously deployed. find a job by the job name.
	if lc.findLaunch(req.ID) != nil {
		return "", fmt.Errorf("cocoon code was previously launched") // TODO: fetch last launch tag and use it
	}

	repoTarURL, err = others.GetGithubRepoRelease(req.URL, req.Tag)
	if err != nil {
		return "", fmt.Errorf("Failed to fetch release from github repo. %s", err)
	}

	// set tag to latest if not provided
	tagStr := req.Tag
	if tagStr == "" {
		tagStr = "latest"
	}

	// determine download directory
	downloadDst = lang.GetDownloadDestination(req.URL)

	// delete download directory if it exists
	if _, err := os.Stat(downloadDst); err == nil {
		log.Info("Download destination is not empty. Deleting content")
		if err = os.RemoveAll(downloadDst); err != nil {
			return "", fmt.Errorf("failed to delete contents of download directory")
		}
		log.Info("Download directory has been deleted")
	}

	// create the download directory
	if err = os.MkdirAll(downloadDst, os.ModePerm); err != nil {
		return "", fmt.Errorf("Failed to create download directory. %s", err)
	}

	log.Infof("Downloading cocoon repository with tag=%s, dst=%s", tagStr, downloadDst)
	filePath := path.Join(downloadDst, fmt.Sprintf("%s.tar.gz", req.ID))
	err = others.DownloadFile(repoTarURL, filePath, func(buf []byte) {})
	if err != nil {
		return "", err
	}

	log.Info("Successfully downloaded cocoon code")
	log.Debugf("Unpacking cocoon code to %s", filePath)

	// unpack tarball
	cmd := "tar"
	args := []string{"-xf", filePath, "-C", downloadDst, "--strip-components", "1"}
	if err = exec.Command(cmd, args...).Run(); err != nil {
		return "", fmt.Errorf("Failed to unpack cocoon code repo tarball. %s", err)
	}

	log.Infof("Successfully unpacked cocoon code to %s", downloadDst)

	os.Remove(filePath)
	log.Info("Deleted the cocoon code tarball")

	return downloadDst, nil
}
<|endoftext|>


<commit_before>/**
 * Copyright 2014 @ z3q.net.
 * name : float.go
 * author : jarryliu
 * date : 2013-12-02 21:34
 * description :
 * history :
 */

package format

import (
	"fmt"
	m "github.com/jsix/gof/math"
	"strconv"
	"strings"
)

func FormatFloat(f float32) string {
	//regexp : ([^\.]+)(\.|(\.[1-9]))0*$  =>  $1$3
	s := fmt.Sprintf("%.2f", f)
	if strings.HasSuffix(s, ".00") {
		return s[:len(s)-3]
	} else if strings.HasSuffix(s, "0") {
		return s[:len(s)-1]
	}
	return s
}

func FormatDecimal(f float32) string {
	return fmt.Sprintf("%.2f", f)
}

func ToDiscountStr(discount int) string {
	if discount == 0 || discount == 100 {
		return ""
	}
	s := strconv.Itoa(discount)
	if s[:1] == "0" {
		return s[:1]
	}
	return s[:1] + "." + s[1:]
}

func RoundAmount(amount float32) float32 {
	return m.Round32(amount, 2)
}

// 普通近似值计算, 不四舍五入,n为小数点精度
func FixedDecimalN(amount float64, n int) float64 {
	return m.FixFloat32(amount, n)
}

func FixedDecimal(amount float64) float64 {
	return m.FixFloat32(amount, 2)
}
<commit_msg>nan<commit_after>/**
 * Copyright 2014 @ z3q.net.
 * name : float.go
 * author : jarryliu
 * date : 2013-12-02 21:34
 * description :
 * history :
 */

package format

import (
	"fmt"
	m "github.com/jsix/gof/math"
	"log"
	"math"
	"strconv"
	"strings"
)

func FormatFloat(f float32) string {
	//regexp : ([^\.]+)(\.|(\.[1-9]))0*$  =>  $1$3
	if math.IsNaN(float64(f)) {
		return "0"
	}
	s := fmt.Sprintf("%.2f", f)
	if s == "NaN" {
		log.Println("----[float][Nan] ", f)
	}
	if strings.HasSuffix(s, ".00") {
		return s[:len(s)-3]
	} else if strings.HasSuffix(s, "0") {
		return s[:len(s)-1]
	}
	return s
}

func FormatDecimal(f float32) string {
	return fmt.Sprintf("%.2f", f)
}

func ToDiscountStr(discount int) string {
	if discount == 0 || discount == 100 {
		return ""
	}
	s := strconv.Itoa(discount)
	if s[:1] == "0" {
		return s[:1]
	}
	return s[:1] + "." + s[1:]
}

func RoundAmount(amount float32) float32 {
	return m.Round32(amount, 2)
}

// 普通近似值计算, 不四舍五入,n为小数点精度
func FixedDecimalN(amount float64, n int) float64 {
	return m.FixFloat32(amount, n)
}

func FixedDecimal(amount float64) float64 {
	return m.FixFloat32(amount, 2)
}
<|endoftext|>


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

// Restrict this (slow) test to builds that specify the tag 'integration'.
// +build integration

package gcscaching_test

import (
	"testing"
	"time"

	"github.com/googlecloudplatform/gcsfuse/timeutil"
	"github.com/jacobsa/gcloud/gcs"
	"github.com/jacobsa/gcloud/gcs/gcscaching"
	"github.com/jacobsa/gcloud/gcs/gcstesting"
	. "github.com/jacobsa/ogletest"
)

func TestIntegration(t *testing.T) { RunTests(t) }

////////////////////////////////////////////////////////////////////////
// Boilerplate
////////////////////////////////////////////////////////////////////////

type IntegrationTest struct {
	cache   gcscaching.StatCache
	clock   timeutil.SimulatedClock
	wrapped gcs.Bucket

	bucket gcs.Bucket
}

func init() { RegisterTestSuite(&IntegrationTest{}) }

func (t *IntegrationTest) SetUp(ti *TestInfo) {
	// Set up a fixed, non-zero time.
	t.clock.SetTime(time.Date(2015, 4, 5, 2, 15, 0, 0, time.Local))

	// Set up dependencies.
	const cacheCapacity = 100
	t.cache = gcscaching.NewStatCache(cacheCapacity)
	t.wrapped = gcstesting.IntegrationTestBucketOrDie()

	t.bucket = gcscaching.NewFastStatBucket(
		ttl,
		t.cache,
		&t.clock,
		t.wrapped)
}

////////////////////////////////////////////////////////////////////////
// Test functions
////////////////////////////////////////////////////////////////////////

func (t *IntegrationTest) DoesFoo() {
	AssertFalse(true, "TODO")
}
<commit_msg>Test names.<commit_after>// Copyright 2015 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Restrict this (slow) test to builds that specify the tag 'integration'.
// +build integration

package gcscaching_test

import (
	"testing"
	"time"

	"github.com/googlecloudplatform/gcsfuse/timeutil"
	"github.com/jacobsa/gcloud/gcs"
	"github.com/jacobsa/gcloud/gcs/gcscaching"
	"github.com/jacobsa/gcloud/gcs/gcstesting"
	. "github.com/jacobsa/ogletest"
)

func TestIntegration(t *testing.T) { RunTests(t) }

////////////////////////////////////////////////////////////////////////
// Boilerplate
////////////////////////////////////////////////////////////////////////

type IntegrationTest struct {
	cache   gcscaching.StatCache
	clock   timeutil.SimulatedClock
	wrapped gcs.Bucket

	bucket gcs.Bucket
}

func init() { RegisterTestSuite(&IntegrationTest{}) }

func (t *IntegrationTest) SetUp(ti *TestInfo) {
	// Set up a fixed, non-zero time.
	t.clock.SetTime(time.Date(2015, 4, 5, 2, 15, 0, 0, time.Local))

	// Set up dependencies.
	const cacheCapacity = 100
	t.cache = gcscaching.NewStatCache(cacheCapacity)
	t.wrapped = gcstesting.IntegrationTestBucketOrDie()

	t.bucket = gcscaching.NewFastStatBucket(
		ttl,
		t.cache,
		&t.clock,
		t.wrapped)
}

////////////////////////////////////////////////////////////////////////
// Test functions
////////////////////////////////////////////////////////////////////////

func (t *IntegrationTest) StatUnknownTwice() {
	AssertFalse(true, "TODO")
}

func (t *IntegrationTest) CreateThenStat() {
	AssertFalse(true, "TODO")
}

func (t *IntegrationTest) ListThenStat() {
	AssertFalse(true, "TODO")
}

func (t *IntegrationTest) CreateThenUpdateThenStat() {
	AssertFalse(true, "TODO")
}

func (t *IntegrationTest) DeleteThenStat() {
	AssertFalse(true, "TODO")
}
<|endoftext|>


<commit_before>/*
Copyright 2017 The Kubernetes Authors.

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

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

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

package assign

import (
	"fmt"
	"regexp"
	"strings"

	"github.com/sirupsen/logrus"

	"k8s.io/test-infra/prow/github"
	"k8s.io/test-infra/prow/pluginhelp"
	"k8s.io/test-infra/prow/plugins"
)

const pluginName = "assign"

var (
	assignRe = regexp.MustCompile(`(?mi)^/(un)?assign(( @?[-\w]+?)*)\s*$`)
	// CCRegexp parses and validates /cc commands, also used by blunderbuss
	CCRegexp = regexp.MustCompile(`(?mi)^/(un)?cc(( +@?[-/\w]+?)*)\s*$`)
)

func init() {
	plugins.RegisterGenericCommentHandler(pluginName, handleGenericComment, helpProvider)
}

func helpProvider(config *plugins.Configuration, enabledRepos []string) (*pluginhelp.PluginHelp, error) {
	// The Config field is omitted because this plugin is not configurable.
	pluginHelp := &pluginhelp.PluginHelp{
		Description: "The assign plugin assigns or requests reviews from users. Specific users can be assigned with the command '/assign @user1' or have reviews requested of them with the command '/cc @user1'. If no user is specified the commands default to targeting the user who created the command. Assignments and requested reviews can be removed in the same way that they are added by prefixing the commands with 'un'.",
	}
	pluginHelp.AddCommand(pluginhelp.Command{
		Usage:       "/[un]assign [[@]<username>...]",
		Description: "Assigns an assignee to the PR",
		Featured:    true,
		WhoCanUse:   "Anyone can use the command, but the target user must be a member of the org that owns the repository.",
		Examples:    []string{"/assign", "/unassign", "/assign @k8s-ci-robot"},
	})
	pluginHelp.AddCommand(pluginhelp.Command{
		Usage:       "/[un]cc [[@]<username>...]",
		Description: "Requests a review from the user(s).",
		Featured:    true,
		WhoCanUse:   "Anyone can use the command, but the target user must be a member of the org that owns the repository.",
		Examples:    []string{"/cc", "/uncc", "/cc @k8s-ci-robot"},
	})
	return pluginHelp, nil
}

type githubClient interface {
	AssignIssue(owner, repo string, number int, logins []string) error
	UnassignIssue(owner, repo string, number int, logins []string) error

	RequestReview(org, repo string, number int, logins []string) error
	UnrequestReview(org, repo string, number int, logins []string) error

	CreateComment(owner, repo string, number int, comment string) error
}

func handleGenericComment(pc plugins.Agent, e github.GenericCommentEvent) error {
	if e.Action != github.GenericCommentActionCreated {
		return nil
	}
	err := handle(newAssignHandler(e, pc.GitHubClient, pc.Logger))
	if e.IsPR {
		err = combineErrors(err, handle(newReviewHandler(e, pc.GitHubClient, pc.Logger)))
	}
	return err
}

func parseLogins(text string) []string {
	var parts []string
	for _, p := range strings.Split(text, " ") {
		t := strings.Trim(p, "@ ")
		if t == "" {
			continue
		}
		parts = append(parts, t)
	}
	return parts
}

func combineErrors(err1, err2 error) error {
	if err1 != nil && err2 != nil {
		return fmt.Errorf("two errors: 1) %v 2) %v", err1, err2)
	} else if err1 != nil {
		return err1
	} else {
		return err2
	}
}

// handle is the generic handler for the assign plugin. It uses the handler's regexp and affectedLogins
// functions to identify the users to add and/or remove and then passes the appropriate users to the
// handler's add and remove functions. If add fails to add some of the users, a response comment is
// created where the body of the response is generated by the handler's addFailureResponse function.
func handle(h *handler) error {
	e := h.event
	org := e.Repo.Owner.Login
	repo := e.Repo.Name
	matches := h.regexp.FindAllStringSubmatch(e.Body, -1)
	if matches == nil {
		return nil
	}
	users := make(map[string]bool)
	for _, re := range matches {
		add := re[1] != "un" // un<cmd> == !add
		if re[2] == "" {
			users[e.User.Login] = add
		} else {
			for _, login := range parseLogins(re[2]) {
				users[login] = add
			}
		}
	}
	var toAdd, toRemove []string
	for login, add := range users {
		if add {
			toAdd = append(toAdd, login)
		} else {
			toRemove = append(toRemove, login)
		}
	}

	if len(toRemove) > 0 {
		h.log.Printf("Removing %s from %s/%s#%d: %v", h.userType, org, repo, e.Number, toRemove)
		if err := h.remove(org, repo, e.Number, toRemove); err != nil {
			return err
		}
	}
	if len(toAdd) > 0 {
		h.log.Printf("Adding %s to %s/%s#%d: %v", h.userType, org, repo, e.Number, toAdd)
		if err := h.add(org, repo, e.Number, toAdd); err != nil {
			if mu, ok := err.(github.MissingUsers); ok {
				msg := h.addFailureResponse(mu)
				if len(msg) == 0 {
					return nil
				}
				if err := h.gc.CreateComment(org, repo, e.Number, plugins.FormatResponseRaw(e.Body, e.HTMLURL, e.User.Login, msg)); err != nil {
					return fmt.Errorf("comment err: %v", err)
				}
				return nil
			}
			return err
		}
	}
	return nil
}

// handler is a struct that contains data about a github event and provides functions to help handle it.
type handler struct {
	// addFailureResponse generates the body of a response comment in the event that the add function fails.
	addFailureResponse func(mu github.MissingUsers) string
	// remove is the function that is called on the affected logins for a command prefixed with 'un'.
	remove func(org, repo string, number int, users []string) error
	// add is the function that is called on the affected logins for a command with no 'un' prefix.
	add func(org, repo string, number int, users []string) error

	// event is a pointer to the github.GenericCommentEvent struct that triggered the handler.
	event *github.GenericCommentEvent
	// regexp is the regular expression describing the command. It must have an optional 'un' prefix
	// as the first subgroup and the arguments to the command as the second subgroup.
	regexp *regexp.Regexp
	// gc is the githubClient to use for creating response comments in the event of a failure.
	gc githubClient

	// log is a logrus.Entry used to record actions the handler takes.
	log *logrus.Entry
	// userType is a string that represents the type of users affected by this handler. (e.g. 'assignees')
	userType string
}

func newAssignHandler(e github.GenericCommentEvent, gc githubClient, log *logrus.Entry) *handler {
	org := e.Repo.Owner.Login
	addFailureResponse := func(mu github.MissingUsers) string {
		return fmt.Sprintf("GitHub didn't allow me to assign the following users: %s.\n\nNote that only [%s members](https://github.com/orgs/%s/people) and repo collaborators can be assigned and that issues/PRs can only have 10 assignees at the same time.\nFor more information please see [the contributor guide](https://git.k8s.io/community/contributors/guide/#issue-assignment-in-github)", strings.Join(mu.Users, ", "), org, org)
	}

	return &handler{
		addFailureResponse: addFailureResponse,
		remove:             gc.UnassignIssue,
		add:                gc.AssignIssue,
		event:              &e,
		regexp:             assignRe,
		gc:                 gc,
		log:                log,
		userType:           "assignee(s)",
	}
}

func newReviewHandler(e github.GenericCommentEvent, gc githubClient, log *logrus.Entry) *handler {
	org := e.Repo.Owner.Login
	addFailureResponse := func(mu github.MissingUsers) string {
		return fmt.Sprintf("GitHub didn't allow me to request PR reviews from the following users: %s.\n\nNote that only [%s members](https://github.com/orgs/%s/people) and repo collaborators can review this PR, and authors cannot review their own PRs.", strings.Join(mu.Users, ", "), org, org)
	}

	return &handler{
		addFailureResponse: addFailureResponse,
		remove:             gc.UnrequestReview,
		add:                gc.RequestReview,
		event:              &e,
		regexp:             CCRegexp,
		gc:                 gc,
		log:                log,
		userType:           "reviewer(s)",
	}
}
<commit_msg>Update command help for assign plugin<commit_after>/*
Copyright 2017 The Kubernetes Authors.

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

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

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

package assign

import (
	"fmt"
	"regexp"
	"strings"

	"github.com/sirupsen/logrus"

	"k8s.io/test-infra/prow/github"
	"k8s.io/test-infra/prow/pluginhelp"
	"k8s.io/test-infra/prow/plugins"
)

const pluginName = "assign"

var (
	assignRe = regexp.MustCompile(`(?mi)^/(un)?assign(( @?[-\w]+?)*)\s*$`)
	// CCRegexp parses and validates /cc commands, also used by blunderbuss
	CCRegexp = regexp.MustCompile(`(?mi)^/(un)?cc(( +@?[-/\w]+?)*)\s*$`)
)

func init() {
	plugins.RegisterGenericCommentHandler(pluginName, handleGenericComment, helpProvider)
}

func helpProvider(config *plugins.Configuration, enabledRepos []string) (*pluginhelp.PluginHelp, error) {
	// The Config field is omitted because this plugin is not configurable.
	pluginHelp := &pluginhelp.PluginHelp{
		Description: "The assign plugin assigns or requests reviews from users. Specific users can be assigned with the command '/assign @user1' or have reviews requested of them with the command '/cc @user1'. If no user is specified the commands default to targeting the user who created the command. Assignments and requested reviews can be removed in the same way that they are added by prefixing the commands with 'un'.",
	}
	pluginHelp.AddCommand(pluginhelp.Command{
		Usage:       "/[un]assign [[@]<username>...]",
		Description: "Assigns an assignee to the PR",
		Featured:    true,
		WhoCanUse:   "Anyone can use the command, but the target user must be an org member, a repo collaborator, or should have previously commented on the issue or PR.",
		Examples:    []string{"/assign", "/unassign", "/assign @k8s-ci-robot"},
	})
	pluginHelp.AddCommand(pluginhelp.Command{
		Usage:       "/[un]cc [[@]<username>...]",
		Description: "Requests a review from the user(s).",
		Featured:    true,
		WhoCanUse:   "Anyone can use the command, but the target user must be a member of the org that owns the repository.",
		Examples:    []string{"/cc", "/uncc", "/cc @k8s-ci-robot"},
	})
	return pluginHelp, nil
}

type githubClient interface {
	AssignIssue(owner, repo string, number int, logins []string) error
	UnassignIssue(owner, repo string, number int, logins []string) error

	RequestReview(org, repo string, number int, logins []string) error
	UnrequestReview(org, repo string, number int, logins []string) error

	CreateComment(owner, repo string, number int, comment string) error
}

func handleGenericComment(pc plugins.Agent, e github.GenericCommentEvent) error {
	if e.Action != github.GenericCommentActionCreated {
		return nil
	}
	err := handle(newAssignHandler(e, pc.GitHubClient, pc.Logger))
	if e.IsPR {
		err = combineErrors(err, handle(newReviewHandler(e, pc.GitHubClient, pc.Logger)))
	}
	return err
}

func parseLogins(text string) []string {
	var parts []string
	for _, p := range strings.Split(text, " ") {
		t := strings.Trim(p, "@ ")
		if t == "" {
			continue
		}
		parts = append(parts, t)
	}
	return parts
}

func combineErrors(err1, err2 error) error {
	if err1 != nil && err2 != nil {
		return fmt.Errorf("two errors: 1) %v 2) %v", err1, err2)
	} else if err1 != nil {
		return err1
	} else {
		return err2
	}
}

// handle is the generic handler for the assign plugin. It uses the handler's regexp and affectedLogins
// functions to identify the users to add and/or remove and then passes the appropriate users to the
// handler's add and remove functions. If add fails to add some of the users, a response comment is
// created where the body of the response is generated by the handler's addFailureResponse function.
func handle(h *handler) error {
	e := h.event
	org := e.Repo.Owner.Login
	repo := e.Repo.Name
	matches := h.regexp.FindAllStringSubmatch(e.Body, -1)
	if matches == nil {
		return nil
	}
	users := make(map[string]bool)
	for _, re := range matches {
		add := re[1] != "un" // un<cmd> == !add
		if re[2] == "" {
			users[e.User.Login] = add
		} else {
			for _, login := range parseLogins(re[2]) {
				users[login] = add
			}
		}
	}
	var toAdd, toRemove []string
	for login, add := range users {
		if add {
			toAdd = append(toAdd, login)
		} else {
			toRemove = append(toRemove, login)
		}
	}

	if len(toRemove) > 0 {
		h.log.Printf("Removing %s from %s/%s#%d: %v", h.userType, org, repo, e.Number, toRemove)
		if err := h.remove(org, repo, e.Number, toRemove); err != nil {
			return err
		}
	}
	if len(toAdd) > 0 {
		h.log.Printf("Adding %s to %s/%s#%d: %v", h.userType, org, repo, e.Number, toAdd)
		if err := h.add(org, repo, e.Number, toAdd); err != nil {
			if mu, ok := err.(github.MissingUsers); ok {
				msg := h.addFailureResponse(mu)
				if len(msg) == 0 {
					return nil
				}
				if err := h.gc.CreateComment(org, repo, e.Number, plugins.FormatResponseRaw(e.Body, e.HTMLURL, e.User.Login, msg)); err != nil {
					return fmt.Errorf("comment err: %v", err)
				}
				return nil
			}
			return err
		}
	}
	return nil
}

// handler is a struct that contains data about a github event and provides functions to help handle it.
type handler struct {
	// addFailureResponse generates the body of a response comment in the event that the add function fails.
	addFailureResponse func(mu github.MissingUsers) string
	// remove is the function that is called on the affected logins for a command prefixed with 'un'.
	remove func(org, repo string, number int, users []string) error
	// add is the function that is called on the affected logins for a command with no 'un' prefix.
	add func(org, repo string, number int, users []string) error

	// event is a pointer to the github.GenericCommentEvent struct that triggered the handler.
	event *github.GenericCommentEvent
	// regexp is the regular expression describing the command. It must have an optional 'un' prefix
	// as the first subgroup and the arguments to the command as the second subgroup.
	regexp *regexp.Regexp
	// gc is the githubClient to use for creating response comments in the event of a failure.
	gc githubClient

	// log is a logrus.Entry used to record actions the handler takes.
	log *logrus.Entry
	// userType is a string that represents the type of users affected by this handler. (e.g. 'assignees')
	userType string
}

func newAssignHandler(e github.GenericCommentEvent, gc githubClient, log *logrus.Entry) *handler {
	org := e.Repo.Owner.Login
	addFailureResponse := func(mu github.MissingUsers) string {
		return fmt.Sprintf("GitHub didn't allow me to assign the following users: %s.\n\nNote that only [%s members](https://github.com/orgs/%s/people), repo collaborators and people who have commented on this issue/PR can be assigned. Additionally, issues/PRs can only have 10 assignees at the same time.\nFor more information please see [the contributor guide](https://git.k8s.io/community/contributors/guide/#issue-assignment-in-github)", strings.Join(mu.Users, ", "), org, org)
	}

	return &handler{
		addFailureResponse: addFailureResponse,
		remove:             gc.UnassignIssue,
		add:                gc.AssignIssue,
		event:              &e,
		regexp:             assignRe,
		gc:                 gc,
		log:                log,
		userType:           "assignee(s)",
	}
}

func newReviewHandler(e github.GenericCommentEvent, gc githubClient, log *logrus.Entry) *handler {
	org := e.Repo.Owner.Login
	addFailureResponse := func(mu github.MissingUsers) string {
		return fmt.Sprintf("GitHub didn't allow me to request PR reviews from the following users: %s.\n\nNote that only [%s members](https://github.com/orgs/%s/people) and repo collaborators can review this PR, and authors cannot review their own PRs.", strings.Join(mu.Users, ", "), org, org)
	}

	return &handler{
		addFailureResponse: addFailureResponse,
		remove:             gc.UnrequestReview,
		add:                gc.RequestReview,
		event:              &e,
		regexp:             CCRegexp,
		gc:                 gc,
		log:                log,
		userType:           "reviewer(s)",
	}
}
<|endoftext|>


<commit_before>package main

import (
    "fmt"
    "encoding/json"
    "net"
    "bufio"
    "sync"
)

type PeerInfo struct {
    Peer string `json:"peer"`
    Latency int `json:"latency"`
}

type UserInfo struct {
    Type string `json:"type" `
    User string `json:"user" `
    Room string `json:"room" `
    Host string `json:"host" `
    Latency []PeerInfo `json:"latency"`
}

type User struct {
    Name string `json:"name"`
    Role string `json:"role"` //enum: "host", "user"
}

type Instruction struct {
    Type string `json:"type"` //enum: "newPeerConnection" "deletePeerConnection"
    Parent string `json:"parent"`
    Child string `json:"child"`
    Host string `json:"host"`
}

type Room struct {
    ID string `json:"roomID"`
    Users []User `json:"users"`
}

func (room *Room) addUser(user User) {
    room.Users = append(room.Users, user)
}

func (room *Room) getUsers() []User {
    return room.Users
}

func (room *Room) removeUser(user User) {
    for i, u := range room.Users {
	if u.Name == user.Name {
	    room.Users = append(room.Users[:i], room.Users[i+1:]...) // The ... is essential
	    return
	}
    }
}

func (room *Room) getHost() User {
    users := room.getUsers()
    for _, u := range users {
	if u.Role == "host" {
	    return u
	}
    }
    return User{}
}

const (
    CONN_HOST = "localhost"
    CONN_PORT = "8888"
    CONN_TYPE = "tcp"
)

var rooms map[string]Room

var mu sync.Mutex

func main() {
    // Listen for incoming connections.
    listener, err := net.Listen(CONN_TYPE, CONN_HOST+":"+CONN_PORT)
    queue := make(chan UserInfo, 10) // Buffered channel with capacity of 10
    ins := make(chan Instruction, 10)
    rooms = make(map[string]Room)

    if err != nil {
	fmt.Println("Error listening:", err.Error())
    }

    // Close the listener when the application closes.
    defer listener.Close()

    for {
	// Listen for an incoming connection.
	conn, err := listener.Accept()

	if err != nil {
	    fmt.Println("Error accepting: ", err.Error())
	    continue
	}

	// Handle connections in a new goroutine.
	go handleRequests(conn, queue)
	go handleTasks(queue, ins) // Potentially need to increase the number of workers
	go handleInstructions(conn, ins)
    }
}

// Handles incoming requests.
func handleRequests(conn net.Conn, queue chan<- UserInfo) {
    fmt.Println("handleRequests is working")
    defer conn.Close()

    input := bufio.NewScanner(conn)
    var userInfo UserInfo

    for input.Scan() {
	text := input.Text()
	byte_text := []byte(text)
	err := json.Unmarshal(byte_text, &userInfo)
	if err != nil {
	    continue
	}
	queue <- userInfo // send userInfo to task queue
    }
}

func handleTasks(queue <-chan UserInfo, ins chan<- Instruction) {
    fmt.Println("handleTasks is working")
    for {
	var userInfo UserInfo
	userInfo = <- queue

	switch userInfo.Type {
	case "newUser": newUserHandler(userInfo, ins)
	case "host": newHostHandler(userInfo, ins)
	case "disconnectedUser": disconnectHandler(userInfo, ins)
	}
	fmt.Printf("New task received -> Type: %s  User: %s  Room: %s\n", userInfo.Type, userInfo.User, userInfo.Room)
    }
}

func newUserHandler(userInfo UserInfo, ins chan<- Instruction) {
    fmt.Println("newUserHandler triggered")
    roomId := userInfo.Room
    //mu.Lock()
    if room, exist := rooms[roomId]; exist {
	fmt.Println(room.getHost())
	user := User{Name: userInfo.User, Role: "user"}
	room.addUser(user)

	/* Send out instructions */
	/* TODO: may need to separate out this part */
	host := room.getHost()
	if host.Role == "host" {
	    ins <- Instruction{Type:"newPeerConnection", Parent: host.Name, Child: user.Name}
	} else {
	    fmt.Println("ERR: Host doesn't exist")
	}
	fmt.Println(room.getUsers())
    }
    //mu.Unlock()
}

func newHostHandler(userInfo UserInfo, ins chan<- Instruction) {
    fmt.Println("newHostHandler triggered")
    roomId := userInfo.Room
    if _, exist := rooms[roomId]; !exist {
	user := User{Name: userInfo.User, Role: "host"}
	users := make([]User, 0)
	users = append(users, user)
	room := Room{ID: roomId, Users: users}
	rooms[roomId] = room;
	fmt.Println(room.getUsers())
	ins <- Instruction{Type:"host", Host: user.Name}
    }
}

func disconnectHandler(userInfo UserInfo, ins chan<- Instruction) {
    fmt.Println("disconnectHandler triggered")
    roomId := userInfo.Room
    if room, exist := rooms[roomId]; exist {
	user := User{Name:userInfo.User, Role:"user"}
	room.removeUser(user)

	/* Send out instruction */
	host := room.getHost()

	if host.Role == "host" {
	    ins <- Instruction{Type:"deletePeerConnection", Parent: host.Name, Child: user.Name}
	} else {
	    fmt.Println("ERR: Host doesn't exist")
	}

	if len(room.getUsers())==0 {
	    delete(rooms, roomId)
	}
	fmt.Println(room.getUsers())
    }
}

func handleInstructions(conn net.Conn, ins <-chan Instruction) {
    fmt.Println("handleInstructions is working")
    for {
	instruction := <- ins
	str, err := json.Marshal(instruction)
	if err != nil {
	    fmt.Println("Error listening:", err.Error())
	    continue
	}
	fmt.Fprintf(conn, "%s", string(str))
	fmt.Println("Instruction Sent")
    }
}<commit_msg>Improve route.go structure - need to implement Graph struct<commit_after>package main

import (
    "fmt"
    "encoding/json"
    "net"
    "bufio"
    "sync"
)

type PeerInfo struct {
    Peer string `json:"peer"`
    Latency int `json:"latency"`
}

type UserInfo struct {
    Type string `json:"type" `
    User string `json:"user" `
    Room string `json:"room" `
    Host string `json:"host" `
    Latency []PeerInfo `json:"latency"`
}

type User struct {
    Name string `json:"name"`
    Role string `json:"role"` //enum: "host", "user"
}

type Instruction struct {
    Type string `json:"type"` //enum: "newPeerConnection" "deletePeerConnection"
    Parent string `json:"parent"`
    Child string `json:"child"`
    Host string `json:"host"`
}

type Room struct {
    ID string `json:"roomID"`
    Users []User `json:"users"`
}

func (room *Room) addUser(user User) {
    room.Users = append(room.Users, user)
}

func (room *Room) getUsers() []User {
    return room.Users
}

func (room *Room) removeUser(user User) {
    for i, u := range room.Users {
	if u.Name == user.Name {
	    room.Users = append(room.Users[:i], room.Users[i+1:]...) // The ... is essential
	    return
	}
    }
}

func (room *Room) getHost() User {
    users := room.getUsers()
    for _, u := range users {
	if u.Role == "host" {
	    return u
	}
    }
    return User{}
}

const (
    CONN_HOST = "localhost"
    CONN_PORT = "8888"
    CONN_TYPE = "tcp"
)

var rooms map(string)chan UserInfo
var openRoom chan (chan UserInfo)
var closeRoom chan (chan UserInfo)

func main() {
    // Listen for incoming connections.
    listener, err := net.Listen(CONN_TYPE, CONN_HOST+":"+CONN_PORT)
    queue := make(chan UserInfo, 10) // Buffered channel with capacity of 10
    ins := make(chan Instruction, 10)
    //rooms = make(map[string]Room)

    if err != nil {
	fmt.Println("Error listening:", err.Error())
    }

    // Close the listener when the application closes.
    defer listener.Close()

    for {
	// Listen for an incoming connection.
	conn, err := listener.Accept()

	if err != nil {
	    fmt.Println("Error accepting: ", err.Error())
	    continue
	}

	// Handle connections in a new goroutine.
	go handleRequests(conn, queue)
	go handleTasks(queue) // Potentially need to increase the number of workers
	go handleRooms(openRoom, closeRoom, ins)
	go handleInstructions(conn, ins)
    }
}

// Handles incoming requests and parse response from json to UserInfo struct
func handleRequests(conn net.Conn, queue chan<- UserInfo) {
    fmt.Println("handleRequests is working")
    defer conn.Close()

    input := bufio.NewScanner(conn)
    var userInfo UserInfo

    for input.Scan() {
	text := input.Text()
	byte_text := []byte(text)
	err := json.Unmarshal(byte_text, &userInfo)
	if err != nil {
	    continue
	}
	queue <- userInfo // send userInfo to task queue
    }
}

func handleRooms() {
    for {
	select {
	case room := <- openRoom:
	    go manageRoom(room)
	case room := <- closeRoom:
	    room <- UserInfo{Type:"close"}
	    delete(rooms, room)
	}
    }
}

func manageRoom(room <-chan UserInfo) {
    defer close(room)

    var graph = new(Graph) // TODO: implement Graph
    var tree = new(Graph)

  loop: //Labeled for loop
    for {
	userInfo := <- room

	switch userInfo.Type {
	case "host":
	    username := userInfo.Host
	    graph.AddNode(username)
	    graph.SetHead(username)
	    ins <- Instruction{Type: "host", Host: username}

	    if userInfo.Latency { // may be unnecessary
		for _, p := range userInfo.Latency {
		    peername := p.Peer
		    weight := p.Latency
		    graph.AddEdge(peername, username, weight)
		}
	    }

	case "newUser":
	    username := userInfo.User
	    graph.AddNode(username)
	    for _, p := range userInfo.Latency {
		peername := p.Peer
		weight := p.Latency
		graph.AddEdge(peername, username, weight)
	    }

	case "disconnectedUser":
	    username := userInfo.User
	    graph.deleteNode(username)

	}

	newTree := graph.GetOptimalTree(2) // parameter is the constraint. 2 means a binary tree

	_, _, addedEdges, removedEdges := Graph.Diff(tree, newTree)  // addedNodes, removedEdges, addedEdges, removedEdges := Graph.Diff(tree, newTree)

	for _, edge := range removedEdges {
	    ins <- Instruction{Type:"deletePeerConnection", Parent: edge.Parent, Child: edge.Child}
	}

	for _, edge := range addedEdges { // assuming addedEdges are sorted in good orders
	    ins <- Instruction{Type:"newPeerConnection", Parent: edge.Parent, Child: edge.Child}
	}

	tree = newTree
    }
}

func handleTasks(queue <-chan UserInfo) {
    for {
	userInfo := <- queue

	switch userInfo.Type {
	case "newUser": newUserHandler(userInfo, ins); break;
	case "host": newHostHandler(userInfo, ins); break;
	case "disconnectedUser": disconnectHandler(userInfo, ins); break;
	}
	fmt.Printf("New task received -> Type: %s  User: %s  Room: %s\n", userInfo.Type, userInfo.User, userInfo.Room)
    }
}

func newUserHandler(userInfo UserInfo, ins chan<- Instruction) {
    roomId := userInfo.Room
    if room, exist := rooms[roomId]; exist {
	room <- userInfo
    } else {
	fmt.Println("ERR: newUserHandler - room doesn't exist")
    }
	/* Send out instructions */
	/* TODO: may need to separate out this part */
	/*
	host := room.getHost()
	if host.Role == "host" {
	    ins <- Instruction{Type:"newPeerConnection", Parent: host.Name, Child: user.Name}
	} else {
	    fmt.Println("ERR: Host doesn't exist")
	}
    }
    */
}

func newHostHandler(userInfo UserInfo) {
    roomId := userInfo.Room
    if _, exist := rooms[roomId]; !exist {
	room := make(chan UserInfo)
	rooms[roomId] = room
	openRoom <- room
	room <- userInfo
    } else {
	fmt.Println("ERR: newHostHandler - room already exists")
    }
	/*
	user := User{Name: userInfo.User, Role: "host"}
	users := make([]User, 0)
	users = append(users, user)
	room := Room{ID: roomId, Users: users}
	rooms[roomId] = room;
	fmt.Println(room.getUsers())
	ins <- Instruction{Type:"host", Host: user.Name}
	*/
}

func disconnectHandler(userInfo UserInfo, ins chan<- Instruction) {
    roomId := userInfo.Room
    if room, exist := rooms[roomId]; exist {
	room <- userInfo
	//room.removeUser(user)

	/* Send out instruction */
	/*
	host := room.getHost()

	if host.Role == "host" {
	    ins <- Instruction{Type:"deletePeerConnection", Parent: host.Name, Child: user.Name}
	} else {
	    fmt.Println("ERR: Host doesn't exist")
	}

	if len(room.getUsers())==0 {
	    delete(rooms, roomId)
	}
	fmt.Println(room.getUsers())
	*/
    }
    else {
	fmt.Println("ERR: disconnectHandler - disconnecting from a room non-existing")
    }
}

func handleInstructions(conn net.Conn, ins <-chan Instruction) {
    fmt.Println("handleInstructions is working")
    for {
	instruction := <- ins
	str, err := json.Marshal(instruction)
	if err != nil {
	    fmt.Println("Error listening:", err.Error())
	    continue
	}
	fmt.Fprintf(conn, "%s", string(str))
	fmt.Println("Instruction Sent")
    }
}<|endoftext|>