Пример #1
0
func (a *GenRandomAction) Run(s *State) error {
	if a.Length == 0 {
		a.Length = 16
	}
	data := interpolate(s, a.Data)
	if data == "" {
		switch a.Encoding {
		case "", "hex":
			data = random.Hex(a.Length)
		case "base64":
			data = base64.StdEncoding.EncodeToString(random.Bytes(a.Length))
		case "base64safe":
			data = random.Base64(a.Length)
		case "uuid":
			data = random.UUID()
		default:
			return fmt.Errorf("bootstrap: unknown random type: %q", a.Encoding)
		}
	}
	s.StepData[a.ID] = &RandomData{Data: data}
	if a.ControllerKey {
		s.SetControllerKey(data)
	}
	return nil
}
Пример #2
0
func (b *azureBackend) Put(tx *postgres.DBTx, info FileInfo, r io.Reader, appendBlob bool) error {
	if appendBlob {
		// TODO(titanous): This is a hack, we should modify the block list.
		existing, err := b.Open(tx, info, false)
		if err != nil {
			return err
		}
		r = io.MultiReader(existing, r)
	}

	info.ExternalID = random.UUID()
	if err := tx.Exec("UPDATE files SET external_id = $2 WHERE file_id = $1", info.ID, info.ExternalID); err != nil {
		return err
	}

	// Create blob that will be filled with blocks
	if err := b.client.CreateBlockBlob(b.container, info.ExternalID); err != nil {
		return err
	}
	var blocks []storage.Block

	// Create blocks
	buf := make([]byte, azureMaxBlockSize)
	for {
		n, err := io.ReadFull(r, buf)
		if err == io.EOF {
			break
		}
		if err != nil && err != io.ErrUnexpectedEOF {
			return err
		}
		data := buf[:n]
		md5sum := md5.Sum(data)
		blockID := base64.StdEncoding.EncodeToString(random.Bytes(16))

		if err := b.client.PutBlockWithLength(
			b.container,
			info.ExternalID,
			blockID,
			uint64(n),
			bytes.NewReader(data),
			map[string]string{"Content-MD5": base64.StdEncoding.EncodeToString(md5sum[:])},
		); err != nil {
			return err
		}
		blocks = append(blocks, storage.Block{ID: blockID, Status: storage.BlockStatusUncommitted})

		if err == io.ErrUnexpectedEOF {
			break
		}
	}

	// Save the list of blocks to the blob
	return b.client.PutBlockList(b.container, info.ExternalID, blocks)
}
Пример #3
0
func createBridge(name, network, natIface string) (*Bridge, error) {
	ipAddr, ipNet, err := net.ParseCIDR(network)
	if err != nil {
		return nil, err
	}
	if err := netlink.CreateBridge(name, true); err != nil {
		return nil, err
	}
	iface, err := net.InterfaceByName(name)
	if err != nil {
		return nil, err
	}

	// We need to explicitly assign the MAC address to avoid it changing to a lower value
	// See: https://github.com/flynn/flynn/issues/223
	b := random.Bytes(5)
	mac := fmt.Sprintf("fe:%02x:%02x:%02x:%02x:%02x", b[0], b[1], b[2], b[3], b[4])
	if err := netlink.NetworkSetMacAddress(iface, mac); err != nil {
		return nil, err
	}

	if err := netlink.NetworkLinkAddIp(iface, ipAddr, ipNet); err != nil {
		return nil, err
	}
	if err := netlink.NetworkLinkUp(iface); err != nil {
		return nil, err
	}
	if err := ioutil.WriteFile("/proc/sys/net/ipv4/ip_forward", []byte("1\n"), 0644); err != nil {
		return nil, err
	}
	if err := setupIPTables(name, natIface); err != nil {
		return nil, err
	}

	bridge := &Bridge{
		name:   name,
		iface:  iface,
		ipAddr: ipAddr,
		ipNet:  ipNet,
		alloc:  ipallocator.New(),
	}
	bridge.alloc.RequestIP(ipNet, ipAddr)
	return bridge, nil
}
Пример #4
0
func (i *Instance) Start() error {
	i.writeInterfaceConfig()

	macRand := random.Bytes(3)
	macaddr := fmt.Sprintf("52:54:00:%02x:%02x:%02x", macRand[0], macRand[1], macRand[2])

	i.Args = append(i.Args,
		"-enable-kvm",
		"-kernel", i.Kernel,
		"-append", `"root=/dev/sda"`,
		"-netdev", "tap,id=vmnic,ifname="+i.tap.Name+",script=no,downscript=no",
		"-device", "virtio-net,netdev=vmnic,mac="+macaddr,
		"-virtfs", "fsdriver=local,path="+i.netFS+",security_model=passthrough,readonly,mount_tag=netfs",
		"-virtfs", "fsdriver=local,path="+i.BackupsDir+",security_model=passthrough,readonly,mount_tag=backupsfs",
		"-nographic",
	)
	if i.Memory != "" {
		i.Args = append(i.Args, "-m", i.Memory)
	}
	if i.Cores > 0 {
		i.Args = append(i.Args, "-smp", strconv.Itoa(i.Cores))
	}
	for n, d := range i.Drives {
		if d.COW {
			fs, err := i.createCOW(d.FS, d.Temp)
			if err != nil {
				i.cleanup()
				return err
			}
			d.FS = fs
		}
		i.Args = append(i.Args, fmt.Sprintf("-%s", n), d.FS)
	}

	i.cmd = exec.Command("sudo", append([]string{"-u", fmt.Sprintf("#%d", i.User), "-g", fmt.Sprintf("#%d", i.Group), "-H", "/usr/bin/qemu-system-x86_64"}, i.Args...)...)
	i.cmd.Stdout = i.Out
	i.cmd.Stderr = i.Out
	var err error
	if err = i.cmd.Start(); err != nil {
		i.cleanup()
	}
	return err
}
Пример #5
0
func (v *vm) Start() error {
	v.writeInterfaceConfig()

	macRand := random.Bytes(3)
	macaddr := fmt.Sprintf("52:54:00:%02x:%02x:%02x", macRand[0], macRand[1], macRand[2])

	v.Args = append(v.Args,
		"-enable-kvm",
		"-kernel", v.Kernel,
		"-append", `"root=/dev/sda"`,
		"-net", "nic,macaddr="+macaddr,
		"-net", "tap,ifname="+v.tap.Name+",script=no,downscript=no",
		"-virtfs", "fsdriver=local,path="+v.netFS+",security_model=passthrough,readonly,mount_tag=netfs",
		"-nographic",
	)
	if v.Memory != "" {
		v.Args = append(v.Args, "-m", v.Memory)
	}
	if v.Cores > 0 {
		v.Args = append(v.Args, "-smp", strconv.Itoa(v.Cores))
	}
	var err error
	for i, d := range v.Drives {
		if d.COW {
			fs, err := v.createCOW(d.FS, d.Temp)
			if err != nil {
				v.cleanup()
				return err
			}
			d.FS = fs
		}
		v.Args = append(v.Args, fmt.Sprintf("-%s", i), d.FS)
	}

	v.cmd = exec.Command("sudo", append([]string{"-u", fmt.Sprintf("#%d", v.User), "-g", fmt.Sprintf("#%d", v.Group), "-H", "/usr/bin/qemu-system-x86_64"}, v.Args...)...)
	v.cmd.Stdout = v.Out
	v.cmd.Stderr = v.Out
	if err = v.cmd.Start(); err != nil {
		v.cleanup()
	}
	return err
}
Пример #6
0
func (s *SchedulerSuite) TestTCPApp(t *c.C) {
	app, _ := s.createApp(t)

	stream, err := s.controllerClient(t).StreamJobEvents(app.ID, 0)
	t.Assert(err, c.IsNil)
	defer stream.Close()

	t.Assert(flynn(t, "/", "-a", app.Name, "scale", "echoer=1"), Succeeds)

	newRoute := flynn(t, "/", "-a", app.Name, "route", "add", "tcp", "-s", "echo-service")
	t.Assert(newRoute, Succeeds)
	t.Assert(newRoute.Output, Matches, `.+ on port \d+`)
	str := strings.Split(strings.TrimSpace(string(newRoute.Output)), " ")
	port := str[len(str)-1]

	waitForJobEvents(t, stream.Events, jobEvents{"echoer": {"up": 1}})
	// use Attempts to give the processes time to start
	if err := Attempts.Run(func() error {
		servAddr := routerIP + ":" + port
		conn, err := net.Dial("tcp", servAddr)
		if err != nil {
			return err
		}
		defer conn.Close()
		echo := random.Bytes(16)
		_, err = conn.Write(echo)
		if err != nil {
			return err
		}
		reply := make([]byte, 16)
		_, err = conn.Read(reply)
		if err != nil {
			return err
		}
		t.Assert(reply, c.DeepEquals, echo)
		return nil
	}); err != nil {
		t.Fatal(err)
	}
}
Пример #7
0
// ConfigureNetworking is called once during host startup and passed the
// strategy and identifier of the networking coordinatior job. Currently the
// only strategy implemented uses flannel.
func (l *LibvirtLXCBackend) ConfigureNetworking(config *host.NetworkConfig) error {
	var err error
	l.bridgeAddr, l.bridgeNet, err = net.ParseCIDR(config.Subnet)
	if err != nil {
		return err
	}
	l.ipalloc.RequestIP(l.bridgeNet, l.bridgeAddr)

	err = netlink.CreateBridge(bridgeName, false)
	bridgeExists := os.IsExist(err)
	if err != nil && !bridgeExists {
		return err
	}

	bridge, err := net.InterfaceByName(bridgeName)
	if err != nil {
		return err
	}
	if !bridgeExists {
		// We need to explicitly assign the MAC address to avoid it changing to a lower value
		// See: https://github.com/flynn/flynn/issues/223
		b := random.Bytes(5)
		bridgeMAC := fmt.Sprintf("fe:%02x:%02x:%02x:%02x:%02x", b[0], b[1], b[2], b[3], b[4])
		if err := netlink.NetworkSetMacAddress(bridge, bridgeMAC); err != nil {
			return err
		}
	}
	currAddrs, err := bridge.Addrs()
	if err != nil {
		return err
	}
	setIP := true
	for _, addr := range currAddrs {
		ip, net, _ := net.ParseCIDR(addr.String())
		if ip.Equal(l.bridgeAddr) && net.String() == l.bridgeNet.String() {
			setIP = false
		} else {
			if err := netlink.NetworkLinkDelIp(bridge, ip, net); err != nil {
				return err
			}
		}
	}
	if setIP {
		if err := netlink.NetworkLinkAddIp(bridge, l.bridgeAddr, l.bridgeNet); err != nil {
			return err
		}
	}
	if err := netlink.NetworkLinkUp(bridge); err != nil {
		return err
	}

	network, err := l.libvirt.LookupNetworkByName(libvirtNetName)
	if err != nil {
		// network doesn't exist
		networkConfig := &lt.Network{
			Name:    libvirtNetName,
			Bridge:  lt.Bridge{Name: bridgeName},
			Forward: lt.Forward{Mode: "bridge"},
		}
		network, err = l.libvirt.NetworkDefineXML(string(networkConfig.XML()))
		if err != nil {
			return err
		}
	}
	active, err := network.IsActive()
	if err != nil {
		return err
	}
	if !active {
		if err := network.Create(); err != nil {
			return err
		}
	}
	if defaultNet, err := l.libvirt.LookupNetworkByName("default"); err == nil {
		// The default network causes dnsmasq to run and bind to all interfaces,
		// including ours. This prevents discoverd from binding its DNS server.
		// We don't use it, so destroy it if it exists.
		defaultNet.Destroy()
	}

	// enable IP forwarding
	if err := ioutil.WriteFile("/proc/sys/net/ipv4/ip_forward", []byte("1\n"), 0644); err != nil {
		return err
	}

	// Set up iptables for outbound traffic masquerading from containers to the
	// rest of the network.
	if err := iptables.EnableOutboundNAT(bridgeName, l.bridgeNet.String()); err != nil {
		return err
	}

	// Read DNS config, discoverd uses the nameservers
	dnsConf, err := dns.ClientConfigFromFile("/etc/resolv.conf")
	if err != nil {
		return err
	}
	config.Resolvers = dnsConf.Servers

	// Write a resolv.conf to be bind-mounted into containers pointing at the
	// future discoverd DNS listener
	if err := os.MkdirAll("/etc/flynn", 0755); err != nil {
		return err
	}
	var resolvSearch string
	if len(dnsConf.Search) > 0 {
		resolvSearch = fmt.Sprintf("search %s\n", strings.Join(dnsConf.Search, " "))
	}
	if err := ioutil.WriteFile("/etc/flynn/resolv.conf", []byte(fmt.Sprintf("%snameserver %s\n", resolvSearch, l.bridgeAddr.String())), 0644); err != nil {
		return err
	}
	l.resolvConf = "/etc/flynn/resolv.conf"

	// Allocate IPs for running jobs
	for i, container := range l.containers {
		if !container.job.Config.HostNetwork {
			var err error
			l.containers[i].IP, err = l.ipalloc.RequestIP(l.bridgeNet, container.IP)
			if err != nil {
				grohl.Log(grohl.Data{"fn": "ConfigureNetworking", "at": "request_ip", "status": "error", "err": err})
			}
		}
	}

	close(l.networkConfigured)

	return nil
}
Пример #8
0
// ConfigureNetworking is called once during host startup and sets up the local
// bridge and forwarding rules for containers.
func (l *LibcontainerBackend) ConfigureNetworking(config *host.NetworkConfig) error {
	log := l.logger.New("fn", "ConfigureNetworking")
	var err error
	l.bridgeAddr, l.bridgeNet, err = net.ParseCIDR(config.Subnet)
	if err != nil {
		return err
	}
	l.ipalloc.RequestIP(l.bridgeNet, l.bridgeAddr)

	err = netlink.CreateBridge(l.bridgeName, false)
	bridgeExists := os.IsExist(err)
	if err != nil && !bridgeExists {
		return err
	}

	bridge, err := net.InterfaceByName(l.bridgeName)
	if err != nil {
		return err
	}
	if !bridgeExists {
		// We need to explicitly assign the MAC address to avoid it changing to a lower value
		// See: https://github.com/flynn/flynn/issues/223
		b := random.Bytes(5)
		bridgeMAC := fmt.Sprintf("fe:%02x:%02x:%02x:%02x:%02x", b[0], b[1], b[2], b[3], b[4])
		if err := netlink.NetworkSetMacAddress(bridge, bridgeMAC); err != nil {
			return err
		}
	}
	currAddrs, err := bridge.Addrs()
	if err != nil {
		return err
	}
	setIP := true
	for _, addr := range currAddrs {
		ip, net, _ := net.ParseCIDR(addr.String())
		if ip.Equal(l.bridgeAddr) && net.String() == l.bridgeNet.String() {
			setIP = false
		} else {
			if err := netlink.NetworkLinkDelIp(bridge, ip, net); err != nil {
				return err
			}
		}
	}
	if setIP {
		if err := netlink.NetworkLinkAddIp(bridge, l.bridgeAddr, l.bridgeNet); err != nil {
			return err
		}
	}
	if err := netlink.NetworkLinkUp(bridge); err != nil {
		return err
	}

	// enable IP forwarding
	if err := ioutil.WriteFile("/proc/sys/net/ipv4/ip_forward", []byte("1\n"), 0644); err != nil {
		return err
	}

	// Set up iptables for outbound traffic masquerading from containers to the
	// rest of the network.
	if err := iptables.EnableOutboundNAT(l.bridgeName, l.bridgeNet.String()); err != nil {
		return err
	}

	// Read DNS config, discoverd uses the nameservers
	dnsConf, err := dns.ClientConfigFromFile("/etc/resolv.conf")
	if err != nil {
		return err
	}
	config.Resolvers = dnsConf.Servers

	// Write a resolv.conf to be bind-mounted into containers pointing at the
	// future discoverd DNS listener
	if err := os.MkdirAll("/etc/flynn", 0755); err != nil {
		return err
	}
	var resolvSearch string
	if len(dnsConf.Search) > 0 {
		resolvSearch = fmt.Sprintf("search %s\n", strings.Join(dnsConf.Search, " "))
	}
	if err := ioutil.WriteFile("/etc/flynn/resolv.conf", []byte(fmt.Sprintf("%snameserver %s\n", resolvSearch, l.bridgeAddr.String())), 0644); err != nil {
		return err
	}
	l.resolvConf = "/etc/flynn/resolv.conf"

	// Allocate IPs for running jobs
	l.containersMtx.Lock()
	defer l.containersMtx.Unlock()
	for _, container := range l.containers {
		if !container.job.Config.HostNetwork {
			if _, err := l.ipalloc.RequestIP(l.bridgeNet, container.IP); err != nil {
				log.Error("error requesting ip", "job.id", container.job.ID, "err", err)
			}
		}
	}

	close(l.networkConfigured)

	return nil
}
Пример #9
0
func (S) TestNonPatterns(c *C) {
	const sep = " "
	var input bytes.Buffer

	key := genPublicKey(c)
	keyBytes := bytes.TrimRight(bytes.TrimSpace(ssh.MarshalAuthorizedKey(key)), "\n")

	// format: host key
	host1Addr := "101.102.103.72"
	input.WriteString(host1Addr)
	input.WriteString(sep)
	input.Write(keyBytes)
	input.WriteString("\n")

	// format: host key
	host2Addr := "test.example.com"
	input.WriteString(host2Addr)
	input.WriteString(sep)
	input.Write(keyBytes)
	input.WriteString("\n")

	// format: @flag [host]:port key
	host3Addr := "3.example.com"
	host3Port := "2222"
	input.WriteString("@revoked")
	input.WriteString(sep)
	input.WriteString("[" + host3Addr + "]:" + host3Port)
	input.WriteString(sep)
	input.Write(keyBytes)
	input.WriteString("\n")

	// format: host,host,host key
	host4Addr := "4.example.com"
	host5Addr := "102.101.72.100"
	host6Addr := "6.example.com"
	input.WriteString(strings.Join([]string{host4Addr, host5Addr, host6Addr}, ","))
	input.WriteString(sep)
	input.Write(keyBytes)
	input.WriteString("\n")

	// format: host,[host]:port,host key
	host7Addr := "7.example.com"
	host8Addr := "102.101.72.100"
	host8Port := "2223"
	host9Addr := "9.example.com"
	input.WriteString(strings.Join([]string{host7Addr, "[" + host8Addr + "]:" + host8Port, host9Addr}, ","))
	input.WriteString(sep)
	input.Write(keyBytes)
	input.WriteString("\n")

	// format: @flag host,host key
	host10Addr := "10.example.com"
	host11Addr := "11.example.com"
	input.WriteString("@revoked")
	input.WriteString(sep)
	input.WriteString(strings.Join([]string{host10Addr, host11Addr}, ","))
	input.WriteString(sep)
	input.Write(keyBytes)
	input.WriteString("\n")

	// format: hashed
	host12Addr := "12.example.com"
	host12Salt := random.Bytes(16)
	host12SaltEncoded := base64.StdEncoding.EncodeToString(host12Salt)
	host12Mac := hmac.New(sha1.New, host12Salt)
	host12Mac.Write([]byte(host12Addr))
	host12MacEncoded := base64.StdEncoding.EncodeToString(host12Mac.Sum(nil))
	input.WriteString("|1|" + host12SaltEncoded + "|" + host12MacEncoded)
	input.WriteString(sep)
	input.Write(keyBytes)
	input.WriteString("\n")

	k, err := Unmarshal(bytes.NewReader(input.Bytes()))
	c.Assert(err, IsNil)

	// Test HostKeyCallback
	addr := &net.TCPAddr{
		Port: 22,
	}
	c.Assert(k.HostKeyCallback(host1Addr+":22", addr, key), IsNil)
	c.Assert(k.HostKeyCallback(host2Addr+":22", addr, key), IsNil)
	c.Assert(k.HostKeyCallback(host3Addr+":2222", &net.TCPAddr{Port: 2222}, key), Equals, HostRevokedError)
	c.Assert(k.HostKeyCallback(host4Addr+":22", addr, key), IsNil)
	c.Assert(k.HostKeyCallback(host5Addr+":22", addr, key), IsNil)
	c.Assert(k.HostKeyCallback(host6Addr+":22", addr, key), IsNil)
	c.Assert(k.HostKeyCallback(host7Addr+":22", addr, key), IsNil)
	c.Assert(k.HostKeyCallback(host8Addr+":2223", &net.TCPAddr{Port: 2223}, key), IsNil)
	c.Assert(k.HostKeyCallback(host9Addr+":22", addr, key), IsNil)
	c.Assert(k.HostKeyCallback(host10Addr+":22", addr, key), Equals, HostRevokedError)
	c.Assert(k.HostKeyCallback(host11Addr+":22", addr, key), Equals, HostRevokedError)
	c.Assert(k.HostKeyCallback("notfound.example.com:22", addr, key), Equals, HostNotFoundError)
	c.Assert(k.HostKeyCallback(host3Addr+":2223", &net.TCPAddr{Port: 2223}, key), Equals, HostNotFoundError)
	c.Assert(k.HostKeyCallback(host1Addr+":2222", &net.TCPAddr{Port: 2222}, key), Equals, HostNotFoundError)
	c.Assert(k.HostKeyCallback(host12Addr+":22", addr, key), IsNil) // hash match

	// Make sure output is the same as input
	var output bytes.Buffer
	c.Assert(k.Marshal(&output), IsNil)
	c.Assert(output.String(), Equals, input.String())

	// Test AppendHost with Writer
	var output2 bytes.Buffer
	var input2 bytes.Buffer
	c.Assert(k.AppendHost("new1.example.com:2223", key, &output2), IsNil)
	input2.WriteString("[new1.example.com]:2223")
	input2.WriteString(sep)
	input2.Write(keyBytes)
	input2.WriteString("\n")
	input.Write(input2.Bytes())
	c.Assert(output2.String(), Equals, input2.String())

	// Test AppendHost without Writer
	c.Assert(k.AppendHost("new2.example.com:22", key, nil), IsNil)
	input.WriteString("new2.example.com")
	input.WriteString(sep)
	input.Write(keyBytes)
	input.WriteString("\n")
	output.Reset()
	c.Assert(k.Marshal(&output), IsNil)
	c.Assert(output.String(), Equals, input.String())
}
Пример #10
0
func NewLibvirtLXCBackend(state *State, portAlloc map[string]*ports.Allocator, volPath, logPath, initPath string) (Backend, error) {
	libvirtc, err := libvirt.NewVirConnection("lxc:///")
	if err != nil {
		return nil, err
	}

	pinkertonCtx, err := pinkerton.BuildContext("aufs", "/var/lib/docker")
	if err != nil {
		return nil, err
	}

	if err := writeResolvConf("/etc/flynn/resolv.conf"); err != nil {
		return nil, fmt.Errorf("Could not create resolv.conf: %s", err)
	}

	b := random.Bytes(5)
	bridgeMAC := fmt.Sprintf("fe:%02x:%02x:%02x:%02x:%02x", b[0], b[1], b[2], b[3], b[4])

	network, err := libvirtc.LookupNetworkByName(libvirtNetName)
	if err != nil {
		n := &lt.Network{
			Name:   libvirtNetName,
			Bridge: lt.Bridge{Name: bridgeName, STP: "off"},
			IP:     lt.IP{Address: bridgeAddr.String(), Netmask: bridgeMask},
			MAC:    lt.MAC{Address: bridgeMAC},
		}
		network, err = libvirtc.NetworkDefineXML(string(n.XML()))
		if err != nil {
			return nil, err
		}
	}
	active, err := network.IsActive()
	if err != nil {
		return nil, err
	}
	if !active {
		if err := network.Create(); err != nil {
			return nil, err
		}
	}
	// We need to explicitly assign the MAC address to avoid it changing to a lower value
	// See: https://github.com/flynn/flynn/issues/223
	if err := netlink.NetworkSetMacAddress(bridgeName, bridgeMAC); err != nil {
		return nil, err
	}

	iptables.RemoveExistingChain("FLYNN", bridgeName)
	chain, err := iptables.NewChain("FLYNN", bridgeName)
	if err != nil {
		return nil, err
	}
	if err := ioutil.WriteFile("/proc/sys/net/ipv4/conf/"+bridgeName+"/route_localnet", []byte("1"), 0666); err != nil {
		return nil, err
	}
	return &LibvirtLXCBackend{
		LogPath:    logPath,
		VolPath:    volPath,
		InitPath:   initPath,
		libvirt:    libvirtc,
		state:      state,
		ports:      portAlloc,
		pinkerton:  pinkertonCtx,
		forwarder:  ports.NewForwarder(net.ParseIP("0.0.0.0"), chain),
		logs:       make(map[string]*logbuf.Log),
		containers: make(map[string]*libvirtContainer),
	}, nil
}
Пример #11
0
func (s *SchedulerSuite) TestTCPApp(t *c.C) {
	r, err := s.client.GetAppRelease("gitreceive")
	t.Assert(err, c.IsNil)
	imageID := r.Processes["app"].Env["SLUGRUNNER_IMAGE_ID"]

	app := &ct.App{}
	t.Assert(s.client.CreateApp(app), c.IsNil)

	artifact := &ct.Artifact{Type: "docker", URI: "https://registry.hub.docker.com/flynn/slugrunner?id=" + imageID}
	t.Assert(s.client.CreateArtifact(artifact), c.IsNil)

	release := &ct.Release{
		ArtifactID: artifact.ID,
		Processes: map[string]ct.ProcessType{
			"echo": {
				Ports:      []ct.Port{{Proto: "tcp"}},
				Cmd:        []string{"sdutil exec -s echo-service:$PORT socat -v tcp-l:$PORT,fork exec:/bin/cat"},
				Entrypoint: []string{"sh", "-c"},
			},
		},
	}
	t.Assert(s.client.CreateRelease(release), c.IsNil)
	t.Assert(s.client.SetAppRelease(app.ID, release.ID), c.IsNil)

	stream, err := s.client.StreamJobEvents(app.ID)
	defer stream.Close()
	if err != nil {
		t.Error(err)
	}

	t.Assert(flynn("/", "-a", app.Name, "scale", "echo=1"), Succeeds)

	newRoute := flynn("/", "-a", app.Name, "route", "add", "tcp", "-s", "echo-service")
	t.Assert(newRoute, Succeeds)
	t.Assert(newRoute.Output, Matches, `.+ on port \d+`)
	str := strings.Split(strings.TrimSpace(string(newRoute.Output)), " ")
	port := str[len(str)-1]

	waitForJobEvents(t, stream.Events, map[string]int{"echo": 1})
	// use Attempts to give the processes time to start
	if err := Attempts.Run(func() error {
		servAddr := routerIP + ":" + port
		conn, err := net.Dial("tcp", servAddr)
		defer conn.Close()
		if err != nil {
			return err
		}
		echo := random.Bytes(16)
		_, err = conn.Write(echo)
		if err != nil {
			return err
		}
		reply := make([]byte, 16)
		_, err = conn.Read(reply)
		if err != nil {
			return err
		}
		t.Assert(reply, c.DeepEquals, echo)
		return nil
	}); err != nil {
		t.Error(err)
	}
}