1// Copyright 2017 Istio Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package util
16
17import (
18	"io/ioutil"
19	"net"
20	"net/http"
21	"os"
22	"strconv"
23	"time"
24
25	"istio.io/pkg/log"
26
27	"istio.io/istio/pilot/pkg/bootstrap"
28	"istio.io/istio/pkg/config/mesh"
29	"istio.io/istio/pkg/keepalive"
30	"istio.io/istio/pkg/test/env"
31
32	"k8s.io/apimachinery/pkg/util/wait"
33)
34
35var (
36	// MockPilotGrpcAddr is the address to be used for grpc connections.
37	MockPilotGrpcAddr string
38
39	// MockPilotHTTPPort is the dynamic port for pilot http
40	MockPilotHTTPPort int
41
42	// MockPilotGrpcPort is the dynamic port for pilot grpc
43	MockPilotGrpcPort int
44)
45
46// TearDownFunc is to be called to tear down a test server.
47type TearDownFunc func()
48
49// EnsureTestServer will ensure a pilot server is running in process and initializes
50// the MockPilotUrl and MockPilotGrpcAddr to allow connections to the test pilot.
51func EnsureTestServer(args ...func(*bootstrap.PilotArgs)) (*bootstrap.Server, TearDownFunc) {
52	server, tearDown, err := setup(args...)
53	if err != nil {
54		log.Errora("Failed to start in-process server: ", err)
55		panic(err)
56	}
57	return server, tearDown
58}
59
60func setup(additionalArgs ...func(*bootstrap.PilotArgs)) (*bootstrap.Server, TearDownFunc, error) {
61	// TODO: point to test data directory
62	// Setting FileDir (--configDir) disables k8s client initialization, including for registries,
63	// and uses a 100ms scan. Must be used with the mock registry (or one of the others)
64	// This limits the options -
65
66	// When debugging a test or running locally it helps having a static port for /debug
67	// "0" is used on shared environment (it's not actually clear if such thing exists since
68	// we run the tests in isolated VMs)
69	pilotHTTP := os.Getenv("PILOT_HTTP")
70	if len(pilotHTTP) == 0 {
71		pilotHTTP = "0"
72	}
73	httpAddr := ":" + pilotHTTP
74
75	meshConfig := mesh.DefaultMeshConfig()
76
77	bootstrap.PilotCertDir = env.IstioSrc + "/tests/testdata/certs/pilot"
78
79	additionalArgs = append([]func(p *bootstrap.PilotArgs){func(p *bootstrap.PilotArgs) {
80		p.Namespace = "testing"
81		p.DiscoveryOptions = bootstrap.DiscoveryServiceOptions{
82			HTTPAddr:        httpAddr,
83			GrpcAddr:        ":0",
84			EnableProfiling: true,
85		}
86		//TODO: start mixer first, get its address
87		p.Mesh = bootstrap.MeshArgs{
88			MixerAddress: "istio-mixer.istio-system:9091",
89		}
90		p.Config = bootstrap.ConfigArgs{
91			KubeConfig: env.IstioSrc + "/tests/util/kubeconfig",
92			// Static testdata, should include all configs we want to test.
93			FileDir: env.IstioSrc + "/tests/testdata/config",
94		}
95		p.MeshConfig = &meshConfig
96		p.MCPOptions.MaxMessageSize = 1024 * 1024 * 4
97		p.KeepaliveOptions = keepalive.DefaultOption()
98		p.ForceStop = true
99
100		// TODO: add the plugins, so local tests are closer to reality and test full generation
101		// Plugins:           bootstrap.DefaultPlugins,
102	}}, additionalArgs...)
103	// Create a test pilot discovery service configured to watch the tempDir.
104	args := bootstrap.NewPilotArgs(additionalArgs...)
105
106	// Create and setup the controller.
107	s, err := bootstrap.NewServer(args)
108	if err != nil {
109		return nil, nil, err
110	}
111
112	stop := make(chan struct{})
113	// Start the server.
114	if err := s.Start(stop); err != nil {
115		return nil, nil, err
116	}
117
118	// Extract the port from the network address.
119	_, port, err := net.SplitHostPort(s.HTTPListener.Addr().String())
120	if err != nil {
121		return nil, nil, err
122	}
123	httpURL := "http://localhost:" + port
124	MockPilotHTTPPort, _ = strconv.Atoi(port)
125
126	_, port, err = net.SplitHostPort(s.GRPCListener.Addr().String())
127	if err != nil {
128		return nil, nil, err
129	}
130	MockPilotGrpcAddr = "localhost:" + port
131	MockPilotGrpcPort, _ = strconv.Atoi(port)
132
133	// Wait a bit for the server to come up.
134	err = wait.Poll(500*time.Millisecond, 5*time.Second, func() (bool, error) {
135		client := &http.Client{Timeout: 1 * time.Second}
136		resp, err := client.Get(httpURL + "/ready")
137		if err != nil {
138			return false, nil
139		}
140		defer func() { _ = resp.Body.Close() }()
141		_, _ = ioutil.ReadAll(resp.Body)
142		if resp.StatusCode == http.StatusOK {
143			return true, nil
144		}
145		return false, nil
146	})
147	return s, func() {
148		close(stop)
149	}, err
150}
151