blob: 14d724bd319dbf5f0b34d966d38230d90d2d57a2 [file] [log] [blame]
Sree Kuchibhotla7b739662015-11-05 10:28:16 -08001#!/usr/bin/env python2.7
2# Copyright 2015, Google Inc.
3# 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,
53 'protocol': 'TCP'} for port in container_port_list]
Sree Kuchibhotla7b739662015-11-05 10:28:16 -080054 }
55 ]
56 }
57 }
Sree Kuchibhotla44ca2c22016-02-16 09:48:36 -080058
59 env_list = [{'name': k, 'value': v} for (k, v) in env_dict.iteritems()]
60 if len(env_list) > 0:
61 body['spec']['containers'][0]['env'] = env_list
Sree Kuchibhotla7b739662015-11-05 10:28:16 -080062
63 # Add the 'Command' and 'Args' attributes if they are passed.
64 # Note:
65 # - 'Command' overrides the ENTRYPOINT in the Docker Image
Sree Kuchibhotla44ca2c22016-02-16 09:48:36 -080066 # - 'Args' override the CMD in Docker image (yes, it is confusing!)
Sree Kuchibhotla7b739662015-11-05 10:28:16 -080067 if len(cmd_list) > 0:
68 body['spec']['containers'][0]['command'] = cmd_list
69 if len(arg_list) > 0:
70 body['spec']['containers'][0]['args'] = arg_list
71 return json.dumps(body)
72
73
sreekbfe37a82015-11-16 18:57:33 -080074def _make_service_config(service_name, pod_name, service_port_list,
Sree Kuchibhotla44ca2c22016-02-16 09:48:36 -080075 container_port_list, is_headless):
Sree Kuchibhotla7b739662015-11-05 10:28:16 -080076 """Creates a string containing the Service definition as required by the Kubernetes API.
77
78 NOTE:
sreekbfe37a82015-11-16 18:57:33 -080079 This creates either a Headless Service or 'LoadBalancer' service depending on
Sree Kuchibhotla7b739662015-11-05 10:28:16 -080080 the is_headless parameter. For Headless services, there is no 'type' attribute
81 and the 'clusterIP' attribute is set to 'None'. Also, if the service is
82 Headless, Kubernetes creates DNS entries for Pods - i.e creates DNS A-records
83 mapping the service's name to the Pods' IPs
84 """
85 if len(container_port_list) != len(service_port_list):
86 print(
87 'ERROR: container_port_list and service_port_list must be of same size')
88 return ''
89 body = {
90 'kind': 'Service',
91 'apiVersion': 'v1',
92 'metadata': {
93 'name': service_name,
94 'labels': {
95 'name': service_name
96 }
97 },
98 'spec': {
99 'ports': [],
100 'selector': {
101 'name': pod_name
102 }
103 }
104 }
105 # Populate the 'ports' list in the 'spec' section. This maps service ports
106 # (port numbers that are exposed by Kubernetes) to container ports (i.e port
107 # numbers that are exposed by your Docker image)
108 for idx in range(len(container_port_list)):
109 port_entry = {
110 'port': service_port_list[idx],
111 'targetPort': container_port_list[idx],
112 'protocol': 'TCP'
113 }
114 body['spec']['ports'].append(port_entry)
115
116 # Make this either a LoadBalancer service or a headless service depending on
117 # the is_headless parameter
118 if is_headless:
119 body['spec']['clusterIP'] = 'None'
120 else:
121 body['spec']['type'] = 'LoadBalancer'
122 return json.dumps(body)
123
124
sreekbfe37a82015-11-16 18:57:33 -0800125def _print_connection_error(msg):
Sree Kuchibhotla6a7bd162015-11-17 11:46:04 -0800126 print('ERROR: Connection failed. Did you remember to run Kubenetes proxy on '
Sree Kuchibhotla7b739662015-11-05 10:28:16 -0800127 'localhost (i.e kubectl proxy --port=<proxy_port>) ?. Error: %s' % msg)
128
Sree Kuchibhotla44ca2c22016-02-16 09:48:36 -0800129
sreekbfe37a82015-11-16 18:57:33 -0800130def _do_post(post_url, api_name, request_body):
Sree Kuchibhotla7b739662015-11-05 10:28:16 -0800131 """Helper to do HTTP POST.
132
133 Note:
134 1) On success, Kubernetes returns a success code of 201(CREATED) not 200(OK)
135 2) A response code of 509(CONFLICT) is interpreted as a success code (since
136 the error is most likely due to the resource already existing). This makes
sreekbfe37a82015-11-16 18:57:33 -0800137 _do_post() idempotent which is semantically desirable.
Sree Kuchibhotla7b739662015-11-05 10:28:16 -0800138 """
139 is_success = True
140 try:
Sree Kuchibhotla44ca2c22016-02-16 09:48:36 -0800141 r = requests.post(post_url,
142 data=request_body,
143 timeout=_REQUEST_TIMEOUT_SECS)
Sree Kuchibhotla7b739662015-11-05 10:28:16 -0800144 if r.status_code == requests.codes.conflict:
145 print('WARN: Looks like the resource already exists. Api: %s, url: %s' %
146 (api_name, post_url))
sreekbfe37a82015-11-16 18:57:33 -0800147 elif r.status_code != requests.codes.created:
148 print('ERROR: %s API returned error. HTTP response: (%d) %s' %
149 (api_name, r.status_code, r.text))
Sree Kuchibhotla7b739662015-11-05 10:28:16 -0800150 is_success = False
Sree Kuchibhotla44ca2c22016-02-16 09:48:36 -0800151 except (requests.exceptions.Timeout,
152 requests.exceptions.ConnectionError) as e:
Sree Kuchibhotla7b739662015-11-05 10:28:16 -0800153 is_success = False
sreekbfe37a82015-11-16 18:57:33 -0800154 _print_connection_error(str(e))
Sree Kuchibhotla7b739662015-11-05 10:28:16 -0800155 return is_success
156
157
sreekbfe37a82015-11-16 18:57:33 -0800158def _do_delete(del_url, api_name):
Sree Kuchibhotla7b739662015-11-05 10:28:16 -0800159 """Helper to do HTTP DELETE.
160
161 Note: A response code of 404(NOT_FOUND) is treated as success to keep
sreekbfe37a82015-11-16 18:57:33 -0800162 _do_delete() idempotent.
Sree Kuchibhotla7b739662015-11-05 10:28:16 -0800163 """
164 is_success = True
165 try:
sreekbfe37a82015-11-16 18:57:33 -0800166 r = requests.delete(del_url, timeout=_REQUEST_TIMEOUT_SECS)
Sree Kuchibhotla7b739662015-11-05 10:28:16 -0800167 if r.status_code == requests.codes.not_found:
168 print('WARN: The resource does not exist. Api: %s, url: %s' %
169 (api_name, del_url))
170 elif r.status_code != requests.codes.ok:
171 print('ERROR: %s API returned error. HTTP response: %s' %
172 (api_name, r.text))
173 is_success = False
Sree Kuchibhotla44ca2c22016-02-16 09:48:36 -0800174 except (requests.exceptions.Timeout,
175 requests.exceptions.ConnectionError) as e:
Sree Kuchibhotla7b739662015-11-05 10:28:16 -0800176 is_success = False
sreekbfe37a82015-11-16 18:57:33 -0800177 _print_connection_error(str(e))
Sree Kuchibhotla7b739662015-11-05 10:28:16 -0800178 return is_success
179
180
181def create_service(kube_host, kube_port, namespace, service_name, pod_name,
182 service_port_list, container_port_list, is_headless):
sreekbfe37a82015-11-16 18:57:33 -0800183 """Creates either a Headless Service or a LoadBalancer Service depending
Sree Kuchibhotla7b739662015-11-05 10:28:16 -0800184 on the is_headless parameter.
185 """
186 post_url = 'http://%s:%d/api/v1/namespaces/%s/services' % (
187 kube_host, kube_port, namespace)
sreekbfe37a82015-11-16 18:57:33 -0800188 request_body = _make_service_config(service_name, pod_name, service_port_list,
Sree Kuchibhotla44ca2c22016-02-16 09:48:36 -0800189 container_port_list, is_headless)
sreekbfe37a82015-11-16 18:57:33 -0800190 return _do_post(post_url, 'Create Service', request_body)
Sree Kuchibhotla7b739662015-11-05 10:28:16 -0800191
192
193def create_pod(kube_host, kube_port, namespace, pod_name, image_name,
Sree Kuchibhotla44ca2c22016-02-16 09:48:36 -0800194 container_port_list, cmd_list, arg_list, env_dict):
Sree Kuchibhotla7b739662015-11-05 10:28:16 -0800195 """Creates a Kubernetes Pod.
196
197 Note that it is generally NOT considered a good practice to directly create
198 Pods. Typically, the recommendation is to create 'Controllers' to create and
199 manage Pods' lifecycle. Currently Kubernetes only supports 'Replication
200 Controller' which creates a configurable number of 'identical Replicas' of
201 Pods and automatically restarts any Pods in case of failures (for eg: Machine
202 failures in Kubernetes). This makes it less flexible for our test use cases
sreekbfe37a82015-11-16 18:57:33 -0800203 where we might want slightly different set of args to each Pod. Hence we
204 directly create Pods and not care much about Kubernetes failures since those
205 are very rare.
Sree Kuchibhotla7b739662015-11-05 10:28:16 -0800206 """
207 post_url = 'http://%s:%d/api/v1/namespaces/%s/pods' % (kube_host, kube_port,
208 namespace)
sreekbfe37a82015-11-16 18:57:33 -0800209 request_body = _make_pod_config(pod_name, image_name, container_port_list,
Sree Kuchibhotla44ca2c22016-02-16 09:48:36 -0800210 cmd_list, arg_list, env_dict)
Sree Kuchibhotla3abacfb2015-11-17 11:42:43 -0800211 return _do_post(post_url, 'Create Pod', request_body)
Sree Kuchibhotla7b739662015-11-05 10:28:16 -0800212
213
214def delete_service(kube_host, kube_port, namespace, service_name):
215 del_url = 'http://%s:%d/api/v1/namespaces/%s/services/%s' % (
216 kube_host, kube_port, namespace, service_name)
sreekbfe37a82015-11-16 18:57:33 -0800217 return _do_delete(del_url, 'Delete Service')
Sree Kuchibhotla7b739662015-11-05 10:28:16 -0800218
219
220def delete_pod(kube_host, kube_port, namespace, pod_name):
221 del_url = 'http://%s:%d/api/v1/namespaces/%s/pods/%s' % (kube_host, kube_port,
222 namespace, pod_name)
sreekbfe37a82015-11-16 18:57:33 -0800223 return _do_delete(del_url, 'Delete Pod')