blob: d14c26ad6ac5fd021ed72c6924a10ba2c0dda395 [file] [log] [blame]
Sree Kuchibhotla7b739662015-11-05 10:28:16 -08001#!/usr/bin/env python2.7
Sree Kuchibhotla559e45b2016-02-19 03:02:16 -08002# Copyright 2015-2016 Google Inc.
Sree Kuchibhotla7b739662015-11-05 10:28:16 -08003# All rights reserved.
4#
5# Redistribution and use in source and binary forms, with or without
6# modification, are permitted provided that the following conditions are
7# met:
8#
9# * Redistributions of source code must retain the above copyright
10# notice, this list of conditions and the following disclaimer.
11# * Redistributions in binary form must reproduce the above
12# copyright notice, this list of conditions and the following disclaimer
13# in the documentation and/or other materials provided with the
14# distribution.
15# * Neither the name of Google Inc. nor the names of its
16# contributors may be used to endorse or promote products derived from
17# this software without specific prior written permission.
18#
19# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30
31import requests
32import json
33
sreekbfe37a82015-11-16 18:57:33 -080034_REQUEST_TIMEOUT_SECS = 10
Sree Kuchibhotla7b739662015-11-05 10:28:16 -080035
Sree Kuchibhotla44ca2c22016-02-16 09:48:36 -080036
sreekbfe37a82015-11-16 18:57:33 -080037def _make_pod_config(pod_name, image_name, container_port_list, cmd_list,
Sree Kuchibhotla44ca2c22016-02-16 09:48:36 -080038 arg_list, env_dict):
Sree Kuchibhotla7b739662015-11-05 10:28:16 -080039 """Creates a string containing the Pod defintion as required by the Kubernetes API"""
40 body = {
41 'kind': 'Pod',
42 'apiVersion': 'v1',
43 'metadata': {
44 'name': pod_name,
45 'labels': {'name': pod_name}
46 },
47 'spec': {
48 'containers': [
49 {
50 'name': pod_name,
51 'image': image_name,
Sree Kuchibhotla44ca2c22016-02-16 09:48:36 -080052 'ports': [{'containerPort': port,
Sree Kuchibhotla559e45b2016-02-19 03:02:16 -080053 'protocol': 'TCP'} for port in container_port_list],
54 'imagePullPolicy': 'Always'
Sree Kuchibhotla7b739662015-11-05 10:28:16 -080055 }
56 ]
57 }
58 }
Sree Kuchibhotla44ca2c22016-02-16 09:48:36 -080059
60 env_list = [{'name': k, 'value': v} for (k, v) in env_dict.iteritems()]
61 if len(env_list) > 0:
62 body['spec']['containers'][0]['env'] = env_list
Sree Kuchibhotla7b739662015-11-05 10:28:16 -080063
64 # Add the 'Command' and 'Args' attributes if they are passed.
65 # Note:
66 # - 'Command' overrides the ENTRYPOINT in the Docker Image
Sree Kuchibhotla44ca2c22016-02-16 09:48:36 -080067 # - 'Args' override the CMD in Docker image (yes, it is confusing!)
Sree Kuchibhotla7b739662015-11-05 10:28:16 -080068 if len(cmd_list) > 0:
69 body['spec']['containers'][0]['command'] = cmd_list
70 if len(arg_list) > 0:
71 body['spec']['containers'][0]['args'] = arg_list
72 return json.dumps(body)
73
74
sreekbfe37a82015-11-16 18:57:33 -080075def _make_service_config(service_name, pod_name, service_port_list,
Sree Kuchibhotla44ca2c22016-02-16 09:48:36 -080076 container_port_list, is_headless):
Sree Kuchibhotla7b739662015-11-05 10:28:16 -080077 """Creates a string containing the Service definition as required by the Kubernetes API.
78
79 NOTE:
sreekbfe37a82015-11-16 18:57:33 -080080 This creates either a Headless Service or 'LoadBalancer' service depending on
Sree Kuchibhotla7b739662015-11-05 10:28:16 -080081 the is_headless parameter. For Headless services, there is no 'type' attribute
82 and the 'clusterIP' attribute is set to 'None'. Also, if the service is
83 Headless, Kubernetes creates DNS entries for Pods - i.e creates DNS A-records
84 mapping the service's name to the Pods' IPs
85 """
86 if len(container_port_list) != len(service_port_list):
87 print(
88 'ERROR: container_port_list and service_port_list must be of same size')
89 return ''
90 body = {
91 'kind': 'Service',
92 'apiVersion': 'v1',
93 'metadata': {
94 'name': service_name,
95 'labels': {
96 'name': service_name
97 }
98 },
99 'spec': {
100 'ports': [],
101 'selector': {
102 'name': pod_name
103 }
104 }
105 }
106 # Populate the 'ports' list in the 'spec' section. This maps service ports
107 # (port numbers that are exposed by Kubernetes) to container ports (i.e port
108 # numbers that are exposed by your Docker image)
109 for idx in range(len(container_port_list)):
110 port_entry = {
111 'port': service_port_list[idx],
112 'targetPort': container_port_list[idx],
113 'protocol': 'TCP'
114 }
115 body['spec']['ports'].append(port_entry)
116
117 # Make this either a LoadBalancer service or a headless service depending on
118 # the is_headless parameter
119 if is_headless:
120 body['spec']['clusterIP'] = 'None'
121 else:
122 body['spec']['type'] = 'LoadBalancer'
123 return json.dumps(body)
124
125
sreekbfe37a82015-11-16 18:57:33 -0800126def _print_connection_error(msg):
Sree Kuchibhotla6a7bd162015-11-17 11:46:04 -0800127 print('ERROR: Connection failed. Did you remember to run Kubenetes proxy on '
Sree Kuchibhotla7b739662015-11-05 10:28:16 -0800128 'localhost (i.e kubectl proxy --port=<proxy_port>) ?. Error: %s' % msg)
129
Sree Kuchibhotla44ca2c22016-02-16 09:48:36 -0800130
sreekbfe37a82015-11-16 18:57:33 -0800131def _do_post(post_url, api_name, request_body):
Sree Kuchibhotla7b739662015-11-05 10:28:16 -0800132 """Helper to do HTTP POST.
133
134 Note:
135 1) On success, Kubernetes returns a success code of 201(CREATED) not 200(OK)
136 2) A response code of 509(CONFLICT) is interpreted as a success code (since
137 the error is most likely due to the resource already existing). This makes
sreekbfe37a82015-11-16 18:57:33 -0800138 _do_post() idempotent which is semantically desirable.
Sree Kuchibhotla7b739662015-11-05 10:28:16 -0800139 """
140 is_success = True
141 try:
Sree Kuchibhotla44ca2c22016-02-16 09:48:36 -0800142 r = requests.post(post_url,
143 data=request_body,
144 timeout=_REQUEST_TIMEOUT_SECS)
Sree Kuchibhotla7b739662015-11-05 10:28:16 -0800145 if r.status_code == requests.codes.conflict:
146 print('WARN: Looks like the resource already exists. Api: %s, url: %s' %
147 (api_name, post_url))
sreekbfe37a82015-11-16 18:57:33 -0800148 elif r.status_code != requests.codes.created:
149 print('ERROR: %s API returned error. HTTP response: (%d) %s' %
150 (api_name, r.status_code, r.text))
Sree Kuchibhotla7b739662015-11-05 10:28:16 -0800151 is_success = False
Sree Kuchibhotla44ca2c22016-02-16 09:48:36 -0800152 except (requests.exceptions.Timeout,
153 requests.exceptions.ConnectionError) as e:
Sree Kuchibhotla7b739662015-11-05 10:28:16 -0800154 is_success = False
sreekbfe37a82015-11-16 18:57:33 -0800155 _print_connection_error(str(e))
Sree Kuchibhotla7b739662015-11-05 10:28:16 -0800156 return is_success
157
158
sreekbfe37a82015-11-16 18:57:33 -0800159def _do_delete(del_url, api_name):
Sree Kuchibhotla7b739662015-11-05 10:28:16 -0800160 """Helper to do HTTP DELETE.
161
162 Note: A response code of 404(NOT_FOUND) is treated as success to keep
sreekbfe37a82015-11-16 18:57:33 -0800163 _do_delete() idempotent.
Sree Kuchibhotla7b739662015-11-05 10:28:16 -0800164 """
165 is_success = True
166 try:
sreekbfe37a82015-11-16 18:57:33 -0800167 r = requests.delete(del_url, timeout=_REQUEST_TIMEOUT_SECS)
Sree Kuchibhotla7b739662015-11-05 10:28:16 -0800168 if r.status_code == requests.codes.not_found:
169 print('WARN: The resource does not exist. Api: %s, url: %s' %
170 (api_name, del_url))
171 elif r.status_code != requests.codes.ok:
172 print('ERROR: %s API returned error. HTTP response: %s' %
173 (api_name, r.text))
174 is_success = False
Sree Kuchibhotla44ca2c22016-02-16 09:48:36 -0800175 except (requests.exceptions.Timeout,
176 requests.exceptions.ConnectionError) as e:
Sree Kuchibhotla7b739662015-11-05 10:28:16 -0800177 is_success = False
sreekbfe37a82015-11-16 18:57:33 -0800178 _print_connection_error(str(e))
Sree Kuchibhotla7b739662015-11-05 10:28:16 -0800179 return is_success
180
181
182def create_service(kube_host, kube_port, namespace, service_name, pod_name,
183 service_port_list, container_port_list, is_headless):
sreekbfe37a82015-11-16 18:57:33 -0800184 """Creates either a Headless Service or a LoadBalancer Service depending
Sree Kuchibhotla7b739662015-11-05 10:28:16 -0800185 on the is_headless parameter.
186 """
187 post_url = 'http://%s:%d/api/v1/namespaces/%s/services' % (
188 kube_host, kube_port, namespace)
sreekbfe37a82015-11-16 18:57:33 -0800189 request_body = _make_service_config(service_name, pod_name, service_port_list,
Sree Kuchibhotla44ca2c22016-02-16 09:48:36 -0800190 container_port_list, is_headless)
sreekbfe37a82015-11-16 18:57:33 -0800191 return _do_post(post_url, 'Create Service', request_body)
Sree Kuchibhotla7b739662015-11-05 10:28:16 -0800192
193
194def create_pod(kube_host, kube_port, namespace, pod_name, image_name,
Sree Kuchibhotla44ca2c22016-02-16 09:48:36 -0800195 container_port_list, cmd_list, arg_list, env_dict):
Sree Kuchibhotla7b739662015-11-05 10:28:16 -0800196 """Creates a Kubernetes Pod.
197
198 Note that it is generally NOT considered a good practice to directly create
199 Pods. Typically, the recommendation is to create 'Controllers' to create and
200 manage Pods' lifecycle. Currently Kubernetes only supports 'Replication
201 Controller' which creates a configurable number of 'identical Replicas' of
202 Pods and automatically restarts any Pods in case of failures (for eg: Machine
203 failures in Kubernetes). This makes it less flexible for our test use cases
sreekbfe37a82015-11-16 18:57:33 -0800204 where we might want slightly different set of args to each Pod. Hence we
205 directly create Pods and not care much about Kubernetes failures since those
206 are very rare.
Sree Kuchibhotla7b739662015-11-05 10:28:16 -0800207 """
208 post_url = 'http://%s:%d/api/v1/namespaces/%s/pods' % (kube_host, kube_port,
209 namespace)
sreekbfe37a82015-11-16 18:57:33 -0800210 request_body = _make_pod_config(pod_name, image_name, container_port_list,
Sree Kuchibhotla44ca2c22016-02-16 09:48:36 -0800211 cmd_list, arg_list, env_dict)
Sree Kuchibhotla3abacfb2015-11-17 11:42:43 -0800212 return _do_post(post_url, 'Create Pod', request_body)
Sree Kuchibhotla7b739662015-11-05 10:28:16 -0800213
214
215def delete_service(kube_host, kube_port, namespace, service_name):
216 del_url = 'http://%s:%d/api/v1/namespaces/%s/services/%s' % (
217 kube_host, kube_port, namespace, service_name)
sreekbfe37a82015-11-16 18:57:33 -0800218 return _do_delete(del_url, 'Delete Service')
Sree Kuchibhotla7b739662015-11-05 10:28:16 -0800219
220
221def delete_pod(kube_host, kube_port, namespace, pod_name):
222 del_url = 'http://%s:%d/api/v1/namespaces/%s/pods/%s' % (kube_host, kube_port,
223 namespace, pod_name)
sreekbfe37a82015-11-16 18:57:33 -0800224 return _do_delete(del_url, 'Delete Pod')