Merge "Add support for HTTPS client"
This commit is contained in:
commit
0ce8251cce
22
README.rst
22
README.rst
@ -106,6 +106,28 @@ running. 4GB memory and 2 vCPUs, is the minimum resource requirement for the VM:
|
||||
|
||||
Now launch pods using kubectl, Undercloud Neutron will serve the networking.
|
||||
|
||||
|
||||
How to watch K8S api-server over HTTPS :
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Add absolute path of client side cert file and key file for K8S server in kuryr.conf::
|
||||
|
||||
[kubernetes]
|
||||
ssl_client_crt_file = <absolute file path eg. /etc/kubernetes/admin.crt>
|
||||
ssl_client_key_file = <absolute file path eg. /etc/kubernetes/admin.key>
|
||||
|
||||
If server ssl certification verification is also to be enabled, add absolute path to the ca cert::
|
||||
|
||||
[kubernetes]
|
||||
ssl_ca_crt_file = <absolute file path eg. /etc/kubernetes/ca.crt>
|
||||
ssl_verify_server_crt = True
|
||||
|
||||
If want to query HTTPS K8S api server with "--insecure" mode::
|
||||
|
||||
[kubernetes]
|
||||
ssl_verify_server_crt = False
|
||||
|
||||
|
||||
Features
|
||||
--------
|
||||
|
||||
|
@ -33,6 +33,15 @@ k8s_opts = [
|
||||
cfg.StrOpt('api_root',
|
||||
help=_("The root URL of the Kubernetes API"),
|
||||
default=os.environ.get('K8S_API', 'http://localhost:8080')),
|
||||
cfg.StrOpt('ssl_client_crt_file',
|
||||
help=_("Absolute path to client cert to connect to HTTPS K8S_API")),
|
||||
cfg.StrOpt('ssl_client_key_file',
|
||||
help=_("Absolute path client key file to connect to HTTPS K8S_API")),
|
||||
cfg.StrOpt('ssl_ca_crt_file',
|
||||
help=_("Absolute path to ca cert file to connect to HTTPS K8S_API")),
|
||||
cfg.BoolOpt('ssl_verify_server_crt',
|
||||
help=_("HTTPS K8S_API server identity verification"),
|
||||
default=False),
|
||||
cfg.StrOpt('pod_project_driver',
|
||||
help=_("The driver to determine OpenStack project for pod ports"),
|
||||
default='default'),
|
||||
|
@ -12,14 +12,16 @@
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import contextlib
|
||||
import itertools
|
||||
import os
|
||||
|
||||
from oslo_log import log as logging
|
||||
from oslo_serialization import jsonutils
|
||||
import requests
|
||||
|
||||
from kuryr.lib._i18n import _
|
||||
from kuryr_kubernetes import config
|
||||
from kuryr_kubernetes import exceptions as exc
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
@ -31,11 +33,33 @@ class K8sClient(object):
|
||||
|
||||
def __init__(self, base_url):
|
||||
self._base_url = base_url
|
||||
cert_file = config.CONF.kubernetes.ssl_client_crt_file
|
||||
key_file = config.CONF.kubernetes.ssl_client_key_file
|
||||
ca_crt_file = config.CONF.kubernetes.ssl_ca_crt_file
|
||||
self.verify_server = config.CONF.kubernetes.ssl_verify_server_crt
|
||||
if cert_file and not os.path.exists(cert_file):
|
||||
raise RuntimeError(
|
||||
_("Unable to find ssl cert_file : %s") % cert_file)
|
||||
if key_file and not os.path.exists(key_file):
|
||||
raise RuntimeError(
|
||||
_("Unable to find ssl key_file : %s") % key_file)
|
||||
if self.verify_server:
|
||||
if not ca_crt_file:
|
||||
raise RuntimeError(
|
||||
_("ssl_ca_crt_file cannot be None"))
|
||||
elif not os.path.exists(ca_crt_file):
|
||||
raise RuntimeError(
|
||||
_("Unable to find ca cert_file : %s") % ca_crt_file)
|
||||
else:
|
||||
self.verify_server = ca_crt_file
|
||||
|
||||
self.cert = (cert_file, key_file)
|
||||
|
||||
def get(self, path):
|
||||
LOG.debug("Get %(path)s", {'path': path})
|
||||
url = self._base_url + path
|
||||
response = requests.get(url)
|
||||
response = requests.get(url, cert=self.cert,
|
||||
verify=self.verify_server)
|
||||
if not response.ok:
|
||||
raise exc.K8sClientException(response.text)
|
||||
return response.json()
|
||||
@ -61,7 +85,7 @@ class K8sClient(object):
|
||||
response = requests.patch(url, data=data, headers={
|
||||
'Content-Type': 'application/merge-patch+json',
|
||||
'Accept': 'application/json',
|
||||
})
|
||||
}, cert=self.cert, verify=self.verify_server)
|
||||
if response.ok:
|
||||
return response.json()['metadata']['annotations']
|
||||
if response.status_code == requests.codes.conflict:
|
||||
@ -88,8 +112,9 @@ class K8sClient(object):
|
||||
|
||||
# TODO(ivc): handle connection errors and retry on failure
|
||||
while True:
|
||||
with contextlib.closing(requests.get(url, params=params,
|
||||
stream=True)) as response:
|
||||
with contextlib.closing(
|
||||
requests.get(url, params=params, stream=True,
|
||||
cert=self.cert, verify=self.verify_server)) as response:
|
||||
if not response.ok:
|
||||
raise exc.K8sClientException(response.text)
|
||||
for line in response.iter_lines(delimiter='\n'):
|
||||
|
@ -29,6 +29,38 @@ class TestK8sClient(test_base.TestCase):
|
||||
super(TestK8sClient, self).setUp()
|
||||
self.base_url = 'http://127.0.0.1:12345'
|
||||
self.client = k8s_client.K8sClient(self.base_url)
|
||||
default_cert = (None, None)
|
||||
self.assertEqual(default_cert, self.client.cert)
|
||||
self.assertEqual(False, self.client.verify_server)
|
||||
|
||||
@mock.patch('os.path.exists')
|
||||
@mock.patch('kuryr_kubernetes.config.CONF')
|
||||
def test_https_client_init(self, m_cfg, m_exist):
|
||||
m_cfg.kubernetes.ssl_client_crt_file = 'dummy_crt_file_path'
|
||||
m_cfg.kubernetes.ssl_client_key_file = 'dummy_key_file_path'
|
||||
m_cfg.kubernetes.ssl_ca_crt_file = 'dummy_ca_file_path'
|
||||
m_cfg.kubernetes.ssl_verify_server_crt = True
|
||||
m_exist.return_value = True
|
||||
test_client = k8s_client.K8sClient(self.base_url)
|
||||
cert = ('dummy_crt_file_path', 'dummy_key_file_path')
|
||||
self.assertEqual(cert, test_client.cert)
|
||||
self.assertEqual('dummy_ca_file_path', test_client.verify_server)
|
||||
|
||||
@mock.patch('kuryr_kubernetes.config.CONF')
|
||||
def test_https_client_init_invalid_client_crt_path(self, m_cfg):
|
||||
m_cfg.kubernetes.ssl_client_crt_file = 'dummy_crt_file_path'
|
||||
m_cfg.kubernetes.ssl_client_key_file = 'dummy_key_file_path'
|
||||
self.assertRaises(RuntimeError, k8s_client.K8sClient, self.base_url)
|
||||
|
||||
@mock.patch('os.path.exists')
|
||||
@mock.patch('kuryr_kubernetes.config.CONF')
|
||||
def test_https_client_init_invalid_ca_path(self, m_cfg, m_exist):
|
||||
m_cfg.kubernetes.ssl_client_crt_file = 'dummy_crt_file_path'
|
||||
m_cfg.kubernetes.ssl_client_key_file = 'dummy_key_file_path'
|
||||
m_cfg.kubernetes.ssl_ca_crt_file = None
|
||||
m_cfg.kubernetes.ssl_verify_server_crt = True
|
||||
m_exist.return_value = True
|
||||
self.assertRaises(RuntimeError, k8s_client.K8sClient, self.base_url)
|
||||
|
||||
@mock.patch('requests.get')
|
||||
def test_get(self, m_get):
|
||||
@ -41,7 +73,8 @@ class TestK8sClient(test_base.TestCase):
|
||||
m_get.return_value = m_resp
|
||||
|
||||
self.assertEqual(ret, self.client.get(path))
|
||||
m_get.assert_called_once_with(self.base_url + path)
|
||||
m_get.assert_called_once_with(self.base_url + path,
|
||||
cert=(None, None), verify=False)
|
||||
|
||||
@mock.patch('requests.get')
|
||||
def test_get_exception(self, m_get):
|
||||
@ -72,7 +105,8 @@ class TestK8sClient(test_base.TestCase):
|
||||
self.assertEqual(annotations, self.client.annotate(
|
||||
path, annotations, resource_version=resource_version))
|
||||
m_patch.assert_called_once_with(self.base_url + path,
|
||||
data=data, headers=mock.ANY)
|
||||
data=data, headers=mock.ANY,
|
||||
cert=(None, None), verify=False)
|
||||
|
||||
@mock.patch('itertools.count')
|
||||
@mock.patch('requests.patch')
|
||||
@ -120,10 +154,12 @@ class TestK8sClient(test_base.TestCase):
|
||||
m_patch.assert_has_calls([
|
||||
mock.call(self.base_url + path,
|
||||
data=conflicting_data,
|
||||
headers=mock.ANY),
|
||||
headers=mock.ANY,
|
||||
cert=(None, None), verify=False),
|
||||
mock.call(self.base_url + path,
|
||||
data=good_data,
|
||||
headers=mock.ANY)])
|
||||
headers=mock.ANY,
|
||||
cert=(None, None), verify=False)])
|
||||
|
||||
@mock.patch('itertools.count')
|
||||
@mock.patch('requests.patch')
|
||||
@ -162,10 +198,12 @@ class TestK8sClient(test_base.TestCase):
|
||||
m_patch.assert_has_calls([
|
||||
mock.call(self.base_url + path,
|
||||
data=annotating_data,
|
||||
headers=mock.ANY),
|
||||
headers=mock.ANY,
|
||||
cert=(None, None), verify=False),
|
||||
mock.call(self.base_url + path,
|
||||
data=resolution_data,
|
||||
headers=mock.ANY)])
|
||||
headers=mock.ANY,
|
||||
cert=(None, None), verify=False)])
|
||||
|
||||
@mock.patch('itertools.count')
|
||||
@mock.patch('requests.patch')
|
||||
@ -196,7 +234,8 @@ class TestK8sClient(test_base.TestCase):
|
||||
resource_version=resource_version)
|
||||
m_patch.assert_called_once_with(self.base_url + path,
|
||||
data=conflicting_data,
|
||||
headers=mock.ANY)
|
||||
headers=mock.ANY,
|
||||
cert=(None, None), verify=False)
|
||||
|
||||
@mock.patch('requests.get')
|
||||
def test_watch(self, m_get):
|
||||
@ -218,7 +257,8 @@ class TestK8sClient(test_base.TestCase):
|
||||
self.assertEqual(cycles, m_get.call_count)
|
||||
self.assertEqual(cycles, m_resp.close.call_count)
|
||||
m_get.assert_called_with(self.base_url + path, stream=True,
|
||||
params={'watch': 'true'})
|
||||
params={'watch': 'true'}, cert=(None, None),
|
||||
verify=False)
|
||||
|
||||
@mock.patch('requests.get')
|
||||
def test_watch_exception(self, m_get):
|
||||
|
Loading…
x
Reference in New Issue
Block a user