Config API for Kubernetes cluster access information

Introduces a new sysinv config API for retrieving the Kubernetes
cluster access information and security credentials (if configured).

The information may be used to configure a remote Kubernetes client
with the required configuration to use the Kubernetes API with
administrative privileges with either client certificate authentication
or token authentication for the kubernetes-admin service account.

The following information is available for each cluster:
  Kubernetes Cluster Name (kubernetes)
  Kubernetes Release Version
  Cluster API Endpoint URL
  Cluster Root CA Certificate
  Admin Client Certificate
  Admin Client Key
  Admin User Name (kubernetes-admin)
  Admin service account token

Story: 2008630
Task: 41836
Signed-off-by: Matt Peters <matt.peters@windriver.com>
Change-Id: Ib81c7fcc3a577c1209ab3a0dd882552ba3d2b9db
This commit is contained in:
Matt Peters 2021-02-23 08:33:18 -06:00
parent 85c31c46ab
commit b08ad875eb
11 changed files with 906 additions and 4 deletions

View File

@ -0,0 +1,177 @@
#
# Copyright (c) 2021 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
import testtools
from cgtsclient.tests import utils
import cgtsclient.v1.kube_cluster
FAKE_CLUSTER = {
"cluster_name": "kubernetes",
"cluster_version": "v1.18.1",
"cluster_api_endpoint": "https://10.10.10.2:6443",
"cluster_ca_cert": (
"-----BEGIN CERTIFICATE-----\n"
"MIIE7TCCAtWgAwIBAgIDAODHMA0GCSqGSIb3DQEBCwUAMB8xEDAOBgNVBAoMB0V0\n"
"Y2QgQ0ExCzAJBgNVBAMMAmNhMB4XDTIxMDIyMDE0MDcxOFoXDTMxMDIxODE0MDcx\n"
"OFowHzEQMA4GA1UECgwHRXRjZCBDQTELMAkGA1UEAwwCY2EwggIiMA0GCSqGSIb3\n"
"DQEBAQUAA4ICDwAwggIKAoICAQDL+NVHmb69Dl+D9M1g0eQa3uq4nThcwQ+gimbU\n"
"GcBPBJmfDmrKvIxLde8RZ+tR+N77mT76qbHtS2KlYgIALJV0ZhujFzmytQ3r0T54\n"
"bzrSfczfvQ5zx8kGc6KWmvi86VeuX26tEuN4Kklg1Lljrl9RJ3JJ7ck6Q92wVO1U\n"
"kQmIWUZWclEOSBQbEj1p5CDZIRxf6ZIE57f1FoFzk9MaVVAOwZKgPiN5XSsHRmRT\n"
"3igMP/X/seZQ7q8+Bg1pGxOwCGhxnxHGTzKXTE5VNXnLH2SYfm/RBrn3FxTE2Rp7\n"
"hAjEnt+XZxw4Eju8oNahnIGVb0JWy1gJ6RMgtyQWs1cky7DfDQiF8RmciLuTx4Gy\n"
"81W5RSelQDqrIQueBJrHBNF1nR7F9lu2+51ZgWeqdqLEwFzyjOFDem6vpskzMO75\n"
"EwZMJlWi3ez/xdkYKqg38QKZRfRiIeoi8BbV4wnSXqyxBJ/DZ1NAwbumbP/GRU7j\n"
"m6RS5wlMznwg55pXpiWLDFmJ7YFu+LU1WxYicE4qjPMYBn0OcMR4b8n/f5vGLd9O\n"
"ZPzTLIt5B+9NqMpqoFePsS4anFFJvvhVEK4WwEFsmdii76bv7pYCBftlsEK7o1Mc\n"
"6YFGoTpNZyDA9BFTp0CB7WArQDxQHikDLQzwpqwVZOjcJQN7Rzf0X4bHtW5NdgMJ\n"
"NIhDCwIDAQABozIwMDANBgNVHREEBjAEggJjYTALBgNVHQ8EBAMCAoQwEgYDVR0T\n"
"AQH/BAgwBgEB/wIBATANBgkqhkiG9w0BAQsFAAOCAgEAuKu0XyKN9ZKHT1KvP1uc\n"
"tzChXkeqQ47ciUbUvM56XvcaoB/4/VMax/2RuNBjC7XqXM+FkwZMBHnmU+hxcZ/Z\n"
"evbF46y/puGqgDFgRKb4iSfo9JxnU5wsMX5lPwlYEuRmbJjyFvDNZdseNtH3Ws/4\n"
"iQUGaiHl9HfOePQlb9QDprbRu/Dp6iE6Wai5fuURIXtB9PP5OD2q333vpYolmXXa\n"
"e9ybwYD8E1X8MLQV0Irh/dJ+5T/eqtWUrZ2YhpCuAawGU35L/1ZqDT4rXW40BcoP\n"
"cYSSr4ryWKGynYGjrnu2EnxHkYqIsgMDS/Jq8CjrZLpZ4E4TagXoZhIOa5Y3Yq9p\n"
"yEH4zskY30BUoP7h8Bp7hZIIJ1LyI1F04mukJdHdVH89mhIkU5RuIOJoiBPOMkQw\n"
"GmRIG8IYQMFxplwtebQrQpE6lnnIE2EdUxxqtpqAqPxnRf6LQg/gtjlGRotKiI9D\n"
"6ypovjCQi49X4WBjiBFnrgma9MsFL2ZOJPX6XpGZ6jqBTAtVMcdb+hsZQMm8/M2Z\n"
"QITmxBO+A1hkXGjofbo145omm5qFcWmbvvrnviv3iShEsCoIFpFnGf8RvWwNapeN\n"
"W4WzyAwY1pQs7Er2KEixiPG7BGaC7KUD3l1kB/IeF0rpnO8rmW/Hq23eLRqtk7mF\n"
"8M4zFA2c4PFD35Vu9ERU20E=\n"
"-----END CERTIFICATE-----\n"),
"admin_client_cert": (
"-----BEGIN CERTIFICATE-----\n"
"MIID/DCCAeSgAwIBAgIIRdk0W8Cf6RkwDQYJKoZIhvcNAQELBQAwHzEQMA4GA1UE\n"
"CgwHRXRjZCBDQTELMAkGA1UEAwwCY2EwHhcNMjEwMjIwMTQwNzE4WhcNMjIwMjIw\n"
"MTQyMzU4WjA0MRcwFQYDVQQKEw5zeXN0ZW06bWFzdGVyczEZMBcGA1UEAxMQa3Vi\n"
"ZXJuZXRlcy1hZG1pbjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJ5x\n"
"iFIxCMDtmbbkmuxPidugAOhcq8KQ7W7xiFKxzzyxEzOyoK3zsyL+vaMKSrq19Tc+\n"
"bFcdm/zLPPS4RtjmUK5VP0Z5dA6a06PHlXJ/CMlZIHIQJolGYfYDg4Ky7oYFQ/KP\n"
"4rtVGvyV7mSdhBdKIelgZ/45zyy10leq2oAWChi9P7kNX2pbwBxgLu1yCuz0f9d1\n"
"hyx+hm11RDpUJKsbqNzgvP9nJUiSIbfcNAv7ut5RcC/mpITBdyiCnMMs6DvpC3ao\n"
"xKTks2XWpxgK3Ay1LYjkpaqtMuYK3dGps0Au5b/fSUlJqfzbD0I6wmZYlZK/x/E9\n"
"+aAALAceGudvBovWxW0CAwEAAaMnMCUwDgYDVR0PAQH/BAQDAgWgMBMGA1UdJQQM\n"
"MAoGCCsGAQUFBwMCMA0GCSqGSIb3DQEBCwUAA4ICAQCkVUfWJ/sHdKN++Uaw+wu2\n"
"GFtBnk50btuKcB9vf9fGJK7LHWI5xSMs0kiCXHi3tpa/LvWj0FFWZZ/2fwaV/hfM\n"
"VJUk0pF2Xjp9IuzFcI/SJWROX/TmZUxFSUL1LMaojdbLqPmIcBRJE9Kd/f0+hmtt\n"
"2v9o8E52F8pTSG98dGAvWBfsaktiUos2FbYAJE2UKX5dTnLBLJws55xHx5isHkb5\n"
"I8wb+NbSlKq2Hs4oR0SAjCo+2P+Ej3YblwitPkhV7AkzljHdyKr/f+QT29qgYrW2\n"
"qi7Ftg/9fBsiiCLjLp+DJrfJQR1YnTVuhv8PCTO46IFzT3zxVe/A3EnKj/kps2y8\n"
"qeMeDHvxEACoSXQoE2yZVyCKqp1FEjawXeAS3QAicFdoSAjhC5FSTnRs28UE6tXB\n"
"VqWUUG0FY2/zwswAfIktClJ492utO0HBJt76HcRfR1699Qmfx6fLFKQUDM6fxJk6\n"
"79QI3S2s3eiCwiPtHOUAz7LC5KV6c75Yq+LABY9eN5K4EI6fuD8cEhfDj3iBb3bB\n"
"0jJp0bFsCpD90Nrx253XiVesHiKhLlvnNUVuAylDcvwt8xVv+uuBl4kpVv4kkyT/\n"
"ApqqvGKcUwQp9jIdY9nSZ/SZRW8QFzf404UVeiH+Ruu6+CCqh2PLAtDnSCPVRt1e\n"
"O+hShAzOqQGF72F6XYlx/g==\n"
"-----END CERTIFICATE-----\n"),
"admin_client_key": (
"-----BEGIN RSA PRIVATE KEY-----\n"
"MIIEpAIBAAKCAQEAnnGIUjEIwO2ZtuSa7E+J26AA6FyrwpDtbvGIUrHPPLETM7Kg\n"
"rfOzIv69owpKurX1Nz5sVx2b/Ms89LhG2OZQrlU/Rnl0DprTo8eVcn8IyVkgchAm\n"
"iUZh9gODgrLuhgVD8o/iu1Ua/JXuZJ2EF0oh6WBn/jnPLLXSV6ragBYKGL0/uQ1f\n"
"alvAHGAu7XIK7PR/13WHLH6GbXVEOlQkqxuo3OC8/2clSJIht9w0C/u63lFwL+ak\n"
"hMF3KIKcwyzoO+kLdqjEpOSzZdanGArcDLUtiOSlqq0y5grd0amzQC7lv99JSUmp\n"
"/NsPQjrCZliVkr/H8T35oAAsBx4a528Gi9bFbQIDAQABAoIBAQCJzUZ57ammWj/x\n"
"oJvZYUgOGvgPH+JG41ONxUYCXiFWsM95jCdRg33Otu3qKl5aSz0Noh4KGnd7gqvu\n"
"T4NWy+Fp7jyNJ763oRLnBAPHxBK5Q+oDKmbJx8wVcnLjronjSBsTkO7qbRd+jUv8\n"
"eD7VHqWl2zI3GsJEKZLaqn9FHWYEot2s17obd//4lJPcBg6kGhHDGkJFm7xvVELa\n"
"VXCIN1E9bAoIgv3pie+O53FH0YoXptvYG4F+ffHGk8/cbdcBJ4oLJqF2mJiwuBbf\n"
"GYa5T/rIoPkrnc+kmGcePC6pPjPxttHvyaWIDQZj4Jcy4oz6tzFUF0oEZ2/JfMBt\n"
"Il13gqylAoGBAMU/oaxXHM//NRQqMlL9R8LYLcnze2rnqt+T0ORUZQGETSSXaTxv\n"
"I4T2wyy9yB583RDVJNXp4T3Js2XnweNj8pRRsCjxY1lkpSOaLVqAw/1HwK1DOSEG\n"
"EqW8s37YOPZWGAYIhpfEbD5y960JUjVsuW71w/5cDWkoi1eyeFVbuXg7AoGBAM2i\n"
"+0A6IrZsy/oIJoF8xmOEH78zEMv8W6Qmfb24N0hdcaFJnM48aF50wk8+/YgMNI7J\n"
"kKR7JJAIQmIFn8tYji9zeeRny4iAclRb6ecglCspvxLzF7tci3ae1snaOFs2wz6b\n"
"MkLSfb4nNf2u3dsJ2Z0tU8Tb7pxCDH/yEjCRA4Z3AoGAM/T58jqUFVnlMmWXEfMz\n"
"puhoz0x6kwNpKDF4kdyFKqwd4eicSNYBpjGV4cAv6Y/8b0WlyU8tDKiHv+0XTn1y\n"
"VY1a+L307IQtV75x+ef3OE1hPIJ7lu5RlSSqp1vvTTwKYfR2950+4ghIo2TUKcx0\n"
"3/yO3v6CbdPHOJeDSQC7TycCgYEAq61XyaU/ecGXAaVwUEaVclvKDVxat5J2B7NC\n"
"4vM65CVvSlIkoWF5WPJtjq9uBvj5oAPTyB4uxji/Awri/2dtPVxQ9UlaeRmTWa5q\n"
"ttVSHj76EJ32wCthG6U8eMTArBYqJsh2y6bj567gumwVOFse3MQM3ZsnuDjEKsU0\n"
"Pmuy370CgYAULotjgbSNBZcJu2a2urX+vtjPwpGSsiKYdVHBvspcwWcu8YaaeAde\n"
"71781PJbFV7v45nT2thc+w9IYemXATH/cOO+JVUMYqZY0c+AOa8bvjnMY5Z6cS6Y\n"
"WJC6NHVmvvFb1YhXjQz2GA9GGBmx9+5/vaPp4aPp+VMfdt9MkEV/NQ==\n"
"-----END RSA PRIVATE KEY-----\n"),
"admin_user": "kubernetes-admin",
"admin_token": (
"ZXlKaGJHY2lPaUpTVXpJMU5pSXNJbXRwWkNJNklpMXpXRFZyUkVreFZqQTVUVTVU"
"UmtOSFVuQTBVSE5PVTNWdlJFaG1RM1ozT1VGMU1UbGZZemhtVFZraWZRLmV5SnBj"
"M01pT2lKcmRXSmxjbTVsZEdWekwzTmxjblpwWTJWaFkyTnZkVzUwSWl3aWEzVmla"
"WEp1WlhSbGN5NXBieTl6WlhKMmFXTmxZV05qYjNWdWRDOXVZVzFsYzNCaFkyVWlP"
"aUpyZFdKbExYTjVjM1JsYlNJc0ltdDFZbVZ5Ym1WMFpYTXVhVzh2YzJWeWRtbGpa"
"V0ZqWTI5MWJuUXZjMlZqY21WMExtNWhiV1VpT2lKcmRXSmxjbTVsZEdWekxXRmti"
"V2x1TFhSdmEyVnVMVFIyYzNCdElpd2lhM1ZpWlhKdVpYUmxjeTVwYnk5elpYSjJh"
"V05sWVdOamIzVnVkQzl6WlhKMmFXTmxMV0ZqWTI5MWJuUXVibUZ0WlNJNkltdDFZ"
"bVZ5Ym1WMFpYTXRZV1J0YVc0aUxDSnJkV0psY201bGRHVnpMbWx2TDNObGNuWnBZ"
"MlZoWTJOdmRXNTBMM05sY25acFkyVXRZV05qYjNWdWRDNTFhV1FpT2lJMFlURm1a"
"VEpqTlMweU5qQTJMVFJoWWpRdFlqTXlNUzB5TjJWak1HRXdZVFkyTnpnaUxDSnpk"
"V0lpT2lKemVYTjBaVzA2YzJWeWRtbGpaV0ZqWTI5MWJuUTZhM1ZpWlMxemVYTjBa"
"VzA2YTNWaVpYSnVaWFJsY3kxaFpHMXBiaUo5LlhyRU5hNXI5SXRwOGJjM25aMVZo"
"ZkJlUEFaQ1l2dU5oUVFLYVhNWXlLVjZmQXFiSENIQi1kVnJUYXcxbWs5YXdIQmVz"
"MXhKUFliVHdzU2dacTZkdFlLYjZuY2RGUUpCYjM2aGJ0NnJ4WnJsZlNYRzFVS2xy"
"MlQ4ZW1KaFVCV3hFSzVXazRLU1ZobnVBcmJDLUU3MDNTd0hVdEU2UUhDWkRGTWFk"
"QUoyajJDNmo2RktoLXIwUWpfQ1I4TzBVUTF4c0I0YW9ZS05rUGUxeFJZSVZKUTFW"
"TjlFdkFaa3lUUFhORDhpUV9hQVFuSlBfUFlCS09OLTAyTnZOY3llVjZ1LWNzdzI3"
"NVAyYXJIeGdLLXZrMG5Ec1FkTkR5S3hBY2t3Skc3bkVyVmJkNVJoY2JiN2gwX2Jx"
"dmt4QnJmaEJ5STE4c3k1WFdQTGE4cThIVVE3d092RlpXUQ==")
}
fixtures = {
'/v1/kube_clusters':
{
'GET': (
{},
{"kube_clusters": [FAKE_CLUSTER]},
),
},
'/v1/kube_clusters/%s' % FAKE_CLUSTER['cluster_name']:
{
'GET': (
{},
FAKE_CLUSTER,
),
},
}
class KubeClusterManagerTest(testtools.TestCase):
def setUp(self):
super(KubeClusterManagerTest, self).setUp()
self.api = utils.FakeAPI(fixtures)
self.mgr = cgtsclient.v1.kube_cluster.KubeClusterManager(self.api)
def test_kube_cluster_list(self):
kube_clusters = self.mgr.list()
expect = [
('GET', '/v1/kube_clusters', {}, None),
]
self.assertEqual(self.api.calls, expect)
self.assertEqual(len(kube_clusters), 1)
def test_kube_cluster_show(self):
kube_cluster = self.mgr.get(FAKE_CLUSTER['cluster_name'])
expect = [
('GET', '/v1/kube_clusters/%s' % FAKE_CLUSTER['cluster_name'], {}, None),
]
self.assertEqual(self.api.calls, expect)
self.assertEqual(kube_cluster.cluster_name,
FAKE_CLUSTER['cluster_name'])
self.assertEqual(kube_cluster.cluster_version,
FAKE_CLUSTER['cluster_version'])
self.assertEqual(kube_cluster.cluster_api_endpoint,
FAKE_CLUSTER['cluster_api_endpoint'])
self.assertEqual(kube_cluster.cluster_ca_cert,
FAKE_CLUSTER['cluster_ca_cert'])
self.assertEqual(kube_cluster.admin_client_cert,
FAKE_CLUSTER['admin_client_cert'])
self.assertEqual(kube_cluster.admin_client_key,
FAKE_CLUSTER['admin_client_key'])
self.assertEqual(kube_cluster.admin_user,
FAKE_CLUSTER['admin_user'])
self.assertEqual(kube_cluster.admin_token,
FAKE_CLUSTER['admin_token'])

View File

@ -0,0 +1,158 @@
#
# Copyright (c) 2021 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
import mock
from cgtsclient.tests import test_shell
from cgtsclient.v1.kube_cluster import KubeCluster
FAKE_CLUSTER = {
"cluster_name": "kubernetes",
"cluster_version": "v1.18.1",
"cluster_api_endpoint": "https://10.10.10.2:6443",
"cluster_ca_cert": (
"-----BEGIN CERTIFICATE-----\n"
"MIIE7TCCAtWgAwIBAgIDAODHMA0GCSqGSIb3DQEBCwUAMB8xEDAOBgNVBAoMB0V0\n"
"Y2QgQ0ExCzAJBgNVBAMMAmNhMB4XDTIxMDIyMDE0MDcxOFoXDTMxMDIxODE0MDcx\n"
"OFowHzEQMA4GA1UECgwHRXRjZCBDQTELMAkGA1UEAwwCY2EwggIiMA0GCSqGSIb3\n"
"DQEBAQUAA4ICDwAwggIKAoICAQDL+NVHmb69Dl+D9M1g0eQa3uq4nThcwQ+gimbU\n"
"GcBPBJmfDmrKvIxLde8RZ+tR+N77mT76qbHtS2KlYgIALJV0ZhujFzmytQ3r0T54\n"
"bzrSfczfvQ5zx8kGc6KWmvi86VeuX26tEuN4Kklg1Lljrl9RJ3JJ7ck6Q92wVO1U\n"
"kQmIWUZWclEOSBQbEj1p5CDZIRxf6ZIE57f1FoFzk9MaVVAOwZKgPiN5XSsHRmRT\n"
"3igMP/X/seZQ7q8+Bg1pGxOwCGhxnxHGTzKXTE5VNXnLH2SYfm/RBrn3FxTE2Rp7\n"
"hAjEnt+XZxw4Eju8oNahnIGVb0JWy1gJ6RMgtyQWs1cky7DfDQiF8RmciLuTx4Gy\n"
"81W5RSelQDqrIQueBJrHBNF1nR7F9lu2+51ZgWeqdqLEwFzyjOFDem6vpskzMO75\n"
"EwZMJlWi3ez/xdkYKqg38QKZRfRiIeoi8BbV4wnSXqyxBJ/DZ1NAwbumbP/GRU7j\n"
"m6RS5wlMznwg55pXpiWLDFmJ7YFu+LU1WxYicE4qjPMYBn0OcMR4b8n/f5vGLd9O\n"
"ZPzTLIt5B+9NqMpqoFePsS4anFFJvvhVEK4WwEFsmdii76bv7pYCBftlsEK7o1Mc\n"
"6YFGoTpNZyDA9BFTp0CB7WArQDxQHikDLQzwpqwVZOjcJQN7Rzf0X4bHtW5NdgMJ\n"
"NIhDCwIDAQABozIwMDANBgNVHREEBjAEggJjYTALBgNVHQ8EBAMCAoQwEgYDVR0T\n"
"AQH/BAgwBgEB/wIBATANBgkqhkiG9w0BAQsFAAOCAgEAuKu0XyKN9ZKHT1KvP1uc\n"
"tzChXkeqQ47ciUbUvM56XvcaoB/4/VMax/2RuNBjC7XqXM+FkwZMBHnmU+hxcZ/Z\n"
"evbF46y/puGqgDFgRKb4iSfo9JxnU5wsMX5lPwlYEuRmbJjyFvDNZdseNtH3Ws/4\n"
"iQUGaiHl9HfOePQlb9QDprbRu/Dp6iE6Wai5fuURIXtB9PP5OD2q333vpYolmXXa\n"
"e9ybwYD8E1X8MLQV0Irh/dJ+5T/eqtWUrZ2YhpCuAawGU35L/1ZqDT4rXW40BcoP\n"
"cYSSr4ryWKGynYGjrnu2EnxHkYqIsgMDS/Jq8CjrZLpZ4E4TagXoZhIOa5Y3Yq9p\n"
"yEH4zskY30BUoP7h8Bp7hZIIJ1LyI1F04mukJdHdVH89mhIkU5RuIOJoiBPOMkQw\n"
"GmRIG8IYQMFxplwtebQrQpE6lnnIE2EdUxxqtpqAqPxnRf6LQg/gtjlGRotKiI9D\n"
"6ypovjCQi49X4WBjiBFnrgma9MsFL2ZOJPX6XpGZ6jqBTAtVMcdb+hsZQMm8/M2Z\n"
"QITmxBO+A1hkXGjofbo145omm5qFcWmbvvrnviv3iShEsCoIFpFnGf8RvWwNapeN\n"
"W4WzyAwY1pQs7Er2KEixiPG7BGaC7KUD3l1kB/IeF0rpnO8rmW/Hq23eLRqtk7mF\n"
"8M4zFA2c4PFD35Vu9ERU20E=\n"
"-----END CERTIFICATE-----\n"),
"admin_client_cert": (
"-----BEGIN CERTIFICATE-----\n"
"MIID/DCCAeSgAwIBAgIIRdk0W8Cf6RkwDQYJKoZIhvcNAQELBQAwHzEQMA4GA1UE\n"
"CgwHRXRjZCBDQTELMAkGA1UEAwwCY2EwHhcNMjEwMjIwMTQwNzE4WhcNMjIwMjIw\n"
"MTQyMzU4WjA0MRcwFQYDVQQKEw5zeXN0ZW06bWFzdGVyczEZMBcGA1UEAxMQa3Vi\n"
"ZXJuZXRlcy1hZG1pbjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJ5x\n"
"iFIxCMDtmbbkmuxPidugAOhcq8KQ7W7xiFKxzzyxEzOyoK3zsyL+vaMKSrq19Tc+\n"
"bFcdm/zLPPS4RtjmUK5VP0Z5dA6a06PHlXJ/CMlZIHIQJolGYfYDg4Ky7oYFQ/KP\n"
"4rtVGvyV7mSdhBdKIelgZ/45zyy10leq2oAWChi9P7kNX2pbwBxgLu1yCuz0f9d1\n"
"hyx+hm11RDpUJKsbqNzgvP9nJUiSIbfcNAv7ut5RcC/mpITBdyiCnMMs6DvpC3ao\n"
"xKTks2XWpxgK3Ay1LYjkpaqtMuYK3dGps0Au5b/fSUlJqfzbD0I6wmZYlZK/x/E9\n"
"+aAALAceGudvBovWxW0CAwEAAaMnMCUwDgYDVR0PAQH/BAQDAgWgMBMGA1UdJQQM\n"
"MAoGCCsGAQUFBwMCMA0GCSqGSIb3DQEBCwUAA4ICAQCkVUfWJ/sHdKN++Uaw+wu2\n"
"GFtBnk50btuKcB9vf9fGJK7LHWI5xSMs0kiCXHi3tpa/LvWj0FFWZZ/2fwaV/hfM\n"
"VJUk0pF2Xjp9IuzFcI/SJWROX/TmZUxFSUL1LMaojdbLqPmIcBRJE9Kd/f0+hmtt\n"
"2v9o8E52F8pTSG98dGAvWBfsaktiUos2FbYAJE2UKX5dTnLBLJws55xHx5isHkb5\n"
"I8wb+NbSlKq2Hs4oR0SAjCo+2P+Ej3YblwitPkhV7AkzljHdyKr/f+QT29qgYrW2\n"
"qi7Ftg/9fBsiiCLjLp+DJrfJQR1YnTVuhv8PCTO46IFzT3zxVe/A3EnKj/kps2y8\n"
"qeMeDHvxEACoSXQoE2yZVyCKqp1FEjawXeAS3QAicFdoSAjhC5FSTnRs28UE6tXB\n"
"VqWUUG0FY2/zwswAfIktClJ492utO0HBJt76HcRfR1699Qmfx6fLFKQUDM6fxJk6\n"
"79QI3S2s3eiCwiPtHOUAz7LC5KV6c75Yq+LABY9eN5K4EI6fuD8cEhfDj3iBb3bB\n"
"0jJp0bFsCpD90Nrx253XiVesHiKhLlvnNUVuAylDcvwt8xVv+uuBl4kpVv4kkyT/\n"
"ApqqvGKcUwQp9jIdY9nSZ/SZRW8QFzf404UVeiH+Ruu6+CCqh2PLAtDnSCPVRt1e\n"
"O+hShAzOqQGF72F6XYlx/g==\n"
"-----END CERTIFICATE-----\n"),
"admin_client_key": (
"-----BEGIN RSA PRIVATE KEY-----\n"
"MIIEpAIBAAKCAQEAnnGIUjEIwO2ZtuSa7E+J26AA6FyrwpDtbvGIUrHPPLETM7Kg\n"
"rfOzIv69owpKurX1Nz5sVx2b/Ms89LhG2OZQrlU/Rnl0DprTo8eVcn8IyVkgchAm\n"
"iUZh9gODgrLuhgVD8o/iu1Ua/JXuZJ2EF0oh6WBn/jnPLLXSV6ragBYKGL0/uQ1f\n"
"alvAHGAu7XIK7PR/13WHLH6GbXVEOlQkqxuo3OC8/2clSJIht9w0C/u63lFwL+ak\n"
"hMF3KIKcwyzoO+kLdqjEpOSzZdanGArcDLUtiOSlqq0y5grd0amzQC7lv99JSUmp\n"
"/NsPQjrCZliVkr/H8T35oAAsBx4a528Gi9bFbQIDAQABAoIBAQCJzUZ57ammWj/x\n"
"oJvZYUgOGvgPH+JG41ONxUYCXiFWsM95jCdRg33Otu3qKl5aSz0Noh4KGnd7gqvu\n"
"T4NWy+Fp7jyNJ763oRLnBAPHxBK5Q+oDKmbJx8wVcnLjronjSBsTkO7qbRd+jUv8\n"
"eD7VHqWl2zI3GsJEKZLaqn9FHWYEot2s17obd//4lJPcBg6kGhHDGkJFm7xvVELa\n"
"VXCIN1E9bAoIgv3pie+O53FH0YoXptvYG4F+ffHGk8/cbdcBJ4oLJqF2mJiwuBbf\n"
"GYa5T/rIoPkrnc+kmGcePC6pPjPxttHvyaWIDQZj4Jcy4oz6tzFUF0oEZ2/JfMBt\n"
"Il13gqylAoGBAMU/oaxXHM//NRQqMlL9R8LYLcnze2rnqt+T0ORUZQGETSSXaTxv\n"
"I4T2wyy9yB583RDVJNXp4T3Js2XnweNj8pRRsCjxY1lkpSOaLVqAw/1HwK1DOSEG\n"
"EqW8s37YOPZWGAYIhpfEbD5y960JUjVsuW71w/5cDWkoi1eyeFVbuXg7AoGBAM2i\n"
"+0A6IrZsy/oIJoF8xmOEH78zEMv8W6Qmfb24N0hdcaFJnM48aF50wk8+/YgMNI7J\n"
"kKR7JJAIQmIFn8tYji9zeeRny4iAclRb6ecglCspvxLzF7tci3ae1snaOFs2wz6b\n"
"MkLSfb4nNf2u3dsJ2Z0tU8Tb7pxCDH/yEjCRA4Z3AoGAM/T58jqUFVnlMmWXEfMz\n"
"puhoz0x6kwNpKDF4kdyFKqwd4eicSNYBpjGV4cAv6Y/8b0WlyU8tDKiHv+0XTn1y\n"
"VY1a+L307IQtV75x+ef3OE1hPIJ7lu5RlSSqp1vvTTwKYfR2950+4ghIo2TUKcx0\n"
"3/yO3v6CbdPHOJeDSQC7TycCgYEAq61XyaU/ecGXAaVwUEaVclvKDVxat5J2B7NC\n"
"4vM65CVvSlIkoWF5WPJtjq9uBvj5oAPTyB4uxji/Awri/2dtPVxQ9UlaeRmTWa5q\n"
"ttVSHj76EJ32wCthG6U8eMTArBYqJsh2y6bj567gumwVOFse3MQM3ZsnuDjEKsU0\n"
"Pmuy370CgYAULotjgbSNBZcJu2a2urX+vtjPwpGSsiKYdVHBvspcwWcu8YaaeAde\n"
"71781PJbFV7v45nT2thc+w9IYemXATH/cOO+JVUMYqZY0c+AOa8bvjnMY5Z6cS6Y\n"
"WJC6NHVmvvFb1YhXjQz2GA9GGBmx9+5/vaPp4aPp+VMfdt9MkEV/NQ==\n"
"-----END RSA PRIVATE KEY-----\n"),
"admin_user": "kubernetes-admin",
"admin_token": (
"ZXlKaGJHY2lPaUpTVXpJMU5pSXNJbXRwWkNJNklpMXpXRFZyUkVreFZqQTVUVTVU"
"UmtOSFVuQTBVSE5PVTNWdlJFaG1RM1ozT1VGMU1UbGZZemhtVFZraWZRLmV5SnBj"
"M01pT2lKcmRXSmxjbTVsZEdWekwzTmxjblpwWTJWaFkyTnZkVzUwSWl3aWEzVmla"
"WEp1WlhSbGN5NXBieTl6WlhKMmFXTmxZV05qYjNWdWRDOXVZVzFsYzNCaFkyVWlP"
"aUpyZFdKbExYTjVjM1JsYlNJc0ltdDFZbVZ5Ym1WMFpYTXVhVzh2YzJWeWRtbGpa"
"V0ZqWTI5MWJuUXZjMlZqY21WMExtNWhiV1VpT2lKcmRXSmxjbTVsZEdWekxXRmti"
"V2x1TFhSdmEyVnVMVFIyYzNCdElpd2lhM1ZpWlhKdVpYUmxjeTVwYnk5elpYSjJh"
"V05sWVdOamIzVnVkQzl6WlhKMmFXTmxMV0ZqWTI5MWJuUXVibUZ0WlNJNkltdDFZ"
"bVZ5Ym1WMFpYTXRZV1J0YVc0aUxDSnJkV0psY201bGRHVnpMbWx2TDNObGNuWnBZ"
"MlZoWTJOdmRXNTBMM05sY25acFkyVXRZV05qYjNWdWRDNTFhV1FpT2lJMFlURm1a"
"VEpqTlMweU5qQTJMVFJoWWpRdFlqTXlNUzB5TjJWak1HRXdZVFkyTnpnaUxDSnpk"
"V0lpT2lKemVYTjBaVzA2YzJWeWRtbGpaV0ZqWTI5MWJuUTZhM1ZpWlMxemVYTjBa"
"VzA2YTNWaVpYSnVaWFJsY3kxaFpHMXBiaUo5LlhyRU5hNXI5SXRwOGJjM25aMVZo"
"ZkJlUEFaQ1l2dU5oUVFLYVhNWXlLVjZmQXFiSENIQi1kVnJUYXcxbWs5YXdIQmVz"
"MXhKUFliVHdzU2dacTZkdFlLYjZuY2RGUUpCYjM2aGJ0NnJ4WnJsZlNYRzFVS2xy"
"MlQ4ZW1KaFVCV3hFSzVXazRLU1ZobnVBcmJDLUU3MDNTd0hVdEU2UUhDWkRGTWFk"
"QUoyajJDNmo2RktoLXIwUWpfQ1I4TzBVUTF4c0I0YW9ZS05rUGUxeFJZSVZKUTFW"
"TjlFdkFaa3lUUFhORDhpUV9hQVFuSlBfUFlCS09OLTAyTnZOY3llVjZ1LWNzdzI3"
"NVAyYXJIeGdLLXZrMG5Ec1FkTkR5S3hBY2t3Skc3bkVyVmJkNVJoY2JiN2gwX2Jx"
"dmt4QnJmaEJ5STE4c3k1WFdQTGE4cThIVVE3d092RlpXUQ==")
}
class KubeClusterTest(test_shell.ShellTest):
def setUp(self):
super(KubeClusterTest, self).setUp()
def tearDown(self):
super(KubeClusterTest, self).tearDown()
@mock.patch('cgtsclient.v1.kube_cluster.KubeClusterManager.list')
@mock.patch('cgtsclient.client._get_ksclient')
@mock.patch('cgtsclient.client._get_endpoint')
def test_kube_cluster_list(self, mock_get_endpoint, mock_get_client,
mock_list):
mock_get_endpoint.return_value = 'http://fakelocalhost:6385/v1'
mock_list.return_value = [KubeCluster(None, FAKE_CLUSTER, True)]
self.make_env()
cluster_results = self.shell("kube-cluster-list")
self.assertIn(FAKE_CLUSTER['cluster_name'], cluster_results)
self.assertIn(FAKE_CLUSTER['cluster_version'], cluster_results)
self.assertIn(FAKE_CLUSTER['cluster_api_endpoint'], cluster_results)
@mock.patch('cgtsclient.v1.kube_cluster.KubeClusterManager.get')
@mock.patch('cgtsclient.client._get_ksclient')
@mock.patch('cgtsclient.client._get_endpoint')
def test_kube_cluster_show(self, mock_get_endpoint, mock_get_client,
mock_get):
mock_get_endpoint.return_value = 'http://fakelocalhost:6385/v1'
mock_get.return_value = KubeCluster(None, FAKE_CLUSTER, True)
self.make_env()
cluster_results = self.shell("kube-cluster-show {}".format(
FAKE_CLUSTER['cluster_name']))
self.assertIn(FAKE_CLUSTER['cluster_name'], cluster_results)
self.assertIn(FAKE_CLUSTER['cluster_version'], cluster_results)
self.assertIn(FAKE_CLUSTER['cluster_api_endpoint'], cluster_results)

View File

@ -13,7 +13,7 @@
# License for the specific language governing permissions and limitations
# under the License.
#
# Copyright (c) 2013-2020 Wind River Systems, Inc.
# Copyright (c) 2013-2021 Wind River Systems, Inc.
#
@ -54,6 +54,7 @@ from cgtsclient.v1 import isensorgroup
from cgtsclient.v1 import istor
from cgtsclient.v1 import isystem
from cgtsclient.v1 import iuser
from cgtsclient.v1 import kube_cluster
from cgtsclient.v1 import kube_host_upgrade
from cgtsclient.v1 import kube_upgrade
from cgtsclient.v1 import kube_version
@ -164,6 +165,7 @@ class Client(http.HTTPClient):
self.fernet = fernet.FernetManager(self)
self.app = app.AppManager(self)
self.host_fs = host_fs.HostFsManager(self)
self.kube_cluster = kube_cluster.KubeClusterManager(self)
self.kube_version = kube_version.KubeVersionManager(self)
self.kube_upgrade = kube_upgrade.KubeUpgradeManager(self)
self.kube_host_upgrade = kube_host_upgrade.KubeHostUpgradeManager(self)

View File

@ -0,0 +1,35 @@
#
# Copyright (c) 2021 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
from cgtsclient.common import base
class KubeCluster(base.Resource):
def __repr__(self):
return "<kube_cluster %s>" % self._info
class KubeClusterManager(base.Manager):
resource_class = KubeCluster
@staticmethod
def _path(name=None):
return '/v1/kube_clusters/%s' % name if name else '/v1/kube_clusters'
def list(self):
"""Retrieve the list of kubernetes clusters known to the system."""
return self._list(self._path(), 'kube_clusters')
def get(self, name):
"""Retrieve the details of a given kubernetes cluster
:param name: kubernetes cluster name
"""
try:
return self._list(self._path(name))[0]
except IndexError:
return None

View File

@ -0,0 +1,34 @@
#
# Copyright (c) 2021 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
from cgtsclient.common import utils
from cgtsclient import exc
from collections import OrderedDict
def _print_kube_cluster_show(kube_cluster):
ordereddata = OrderedDict(sorted(kube_cluster.to_dict().items(),
key=lambda t: t[0]))
utils.print_dict(ordereddata, wrap=72)
def do_kube_cluster_list(cc, args):
"""List all kubernetes clusters"""
versions = cc.kube_cluster.list()
fields = ['cluster_name', 'cluster_version', 'cluster_api_endpoint']
labels = fields
utils.print_list(versions, fields, labels, sortby=0)
@utils.arg('name', metavar="<cluster-name>",
help="Kubernetes cluster name", default=None)
def do_kube_cluster_show(cc, args):
"""Show kubernetes cluster details"""
try:
name = cc.kube_cluster.get(args.name)
_print_kube_cluster_show(name)
except exc.HTTPNotFound:
raise exc.CommandError('kubernetes cluster not found: %s' % args.name)

View File

@ -1,5 +1,5 @@
#
# Copyright (c) 2013-2020 Wind River Systems, Inc.
# Copyright (c) 2013-2021 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
@ -42,6 +42,7 @@ from cgtsclient.v1 import istor_shell
from cgtsclient.v1 import isystem_shell
from cgtsclient.v1 import iuser_shell
from cgtsclient.v1 import kube_cluster_shell
from cgtsclient.v1 import kube_upgrade_shell
from cgtsclient.v1 import kube_version_shell
from cgtsclient.v1 import label_shell
@ -121,6 +122,7 @@ COMMAND_MODULES = [
label_shell,
app_shell,
host_fs_shell,
kube_cluster_shell,
kube_version_shell,
kube_upgrade_shell,
device_image_shell,

View File

@ -1,5 +1,5 @@
#
# Copyright (c) 2013-2020 Wind River Systems, Inc.
# Copyright (c) 2013-2021 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
@ -41,6 +41,7 @@ from sysinv.api.controllers.v1 import health
from sysinv.api.controllers.v1 import helm_charts
from sysinv.api.controllers.v1 import host
from sysinv.api.controllers.v1 import kube_app
from sysinv.api.controllers.v1 import kube_cluster
from sysinv.api.controllers.v1 import kube_host_upgrade
from sysinv.api.controllers.v1 import kube_upgrade
from sysinv.api.controllers.v1 import kube_version
@ -252,6 +253,9 @@ class V1(base.APIBase):
host_fs = [link.Link]
"Links to the host_fs resource"
kube_clusters = [link.Link]
"Links to the kube_cluster resource"
kube_versions = [link.Link]
"Links to the kube_version resource"
@ -793,6 +797,13 @@ class V1(base.APIBase):
'host_fs', '',
bookmark=True)]
v1.kube_clusters = [link.Link.make_link('self', pecan.request.host_url,
'kube_clusters', ''),
link.Link.make_link('bookmark',
pecan.request.host_url,
'kube_clusters', '',
bookmark=True)]
v1.kube_versions = [link.Link.make_link('self', pecan.request.host_url,
'kube_versions', ''),
link.Link.make_link('bookmark',
@ -911,6 +922,7 @@ class Controller(rest.RestController):
datanetworks = datanetwork.DataNetworkController()
interface_datanetworks = interface_datanetwork.InterfaceDataNetworkController()
host_fs = host_fs.HostFsController()
kube_clusters = kube_cluster.KubeClusterController()
kube_versions = kube_version.KubeVersionController()
kube_upgrade = kube_upgrade.KubeUpgradeController()
kube_host_upgrades = kube_host_upgrade.KubeHostUpgradeController()

View File

@ -0,0 +1,131 @@
#
# Copyright (c) 2021 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
import pecan
from pecan import rest
from wsme import types as wtypes
import wsmeext.pecan as wsme_pecan
from six.moves.urllib.parse import urlparse
from sysinv.api.controllers.v1 import base
from sysinv.api.controllers.v1 import collection
from sysinv.common import constants
from sysinv.common import kubernetes
from sysinv.common import utils
class KubeCluster(base.APIBase):
"""API representation of a Kubernetes cluster."""
cluster_name = wtypes.text
"Cluster name"
cluster_version = wtypes.text
"Cluster active version"
cluster_api_endpoint = wtypes.text
"Cluster Public API Endpoint URL"
cluster_ca_cert = wtypes.text
"Cluster Root CA Certificate Data"
admin_client_cert = wtypes.text
"Administrative Client Certificate Data"
admin_client_key = wtypes.text
"Administrative Client Key Data"
admin_user = wtypes.text
"Administrative User Name"
admin_token = wtypes.text
"Administrative Service Account Token (base64 encoded)"
@classmethod
def convert(cls, kube_cluster_data):
return KubeCluster(**kube_cluster_data)
class KubeClusterCollection(collection.Collection):
"""API representation of a collection of Kubernetes clusters."""
kube_clusters = [KubeCluster]
"A list containing Kubernetes cluster objects"
def __init__(self, **kwargs):
self._type = 'kube_clusters'
@classmethod
def convert(cls, kube_cluster_list):
collection = KubeClusterCollection()
collection.kube_clusters = [KubeCluster.convert(d)
for d in kube_cluster_list]
return collection
class KubeClusterController(rest.RestController):
"""REST controller for Kubernetes clusters."""
def __init__(self, parent=None, **kwargs):
self._parent = parent
self._kube_operator = kubernetes.KubeOperator()
@wsme_pecan.wsexpose(KubeClusterCollection)
def get_all(self):
"""Retrieve a list of Kubernetes clusters."""
# Currently only a single cluster is supported
kube_cluster = self._get_kube_cluster(kubernetes.KUBERNETES_CLUSTER_DEFAULT)
kube_clusters = [kube_cluster]
return KubeClusterCollection.convert(kube_clusters)
@wsme_pecan.wsexpose(KubeCluster, wtypes.text)
def get_one(self, name=kubernetes.KUBERNETES_CLUSTER_DEFAULT):
"""Retrieve information about the given Kubernetes cluster."""
kube_cluster = self._get_kube_cluster(name)
return KubeCluster.convert(kube_cluster)
def _get_kube_cluster(self, cluster_name):
# Get the current version information
cluster_version = self._kube_operator.kube_get_kubernetes_version()
# Retrieve the default kubernetes cluster configuration
cluster_config = self._kube_operator.kube_get_kubernetes_config()
cluster_ca_cert = utils.get_file_content(cluster_config.ssl_ca_cert)
admin_client_cert = utils.get_file_content(cluster_config.cert_file)
admin_client_key = utils.get_file_content(cluster_config.key_file)
# Build public endpoint from private endpoint
endpoint_parsed = urlparse(cluster_config.host)
endpoint_host = utils.format_url_address(self._get_oam_address())
endpoint_netloc = "{}:{}".format(endpoint_host, endpoint_parsed.port)
cluster_api_endpoint = endpoint_parsed._replace(
netloc=endpoint_netloc).geturl()
# Retrieve the default cluster admin service account token
admin_user = kubernetes.KUBERNETES_ADMIN_USER
admin_token = self._kube_operator.kube_get_service_account_token(
admin_user, kubernetes.NAMESPACE_KUBE_SYSTEM)
return {
"cluster_name": cluster_name,
"cluster_version": cluster_version,
'cluster_api_endpoint': cluster_api_endpoint,
"cluster_ca_cert": cluster_ca_cert,
"admin_client_cert": admin_client_cert,
"admin_client_key": admin_client_key,
"admin_user": admin_user,
"admin_token": admin_token
}
def _get_oam_address(self):
address_name = utils.format_address_name(
constants.CONTROLLER_HOSTNAME, constants.NETWORK_TYPE_OAM)
address = pecan.request.dbapi.address_get_by_name(address_name)
return address.address

View File

@ -1,5 +1,5 @@
#
# Copyright (c) 2013-2020 Wind River Systems, Inc.
# Copyright (c) 2013-2021 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
@ -28,11 +28,18 @@ from six.moves import http_client as httplib
from oslo_log import log as logging
from sysinv.common import exception
LOG = logging.getLogger(__name__)
# Kubernetes Files
KUBERNETES_ADMIN_CONF = '/etc/kubernetes/admin.conf'
# Kubernetes clusters
KUBERNETES_CLUSTER_DEFAULT = "kubernetes"
# Kubernetes users
KUBERNETES_ADMIN_USER = "kubernetes-admin"
# Possible states for each supported kubernetes version
KUBE_STATE_AVAILABLE = 'available'
KUBE_STATE_ACTIVE = 'active'
@ -171,6 +178,7 @@ class KubeOperator(object):
c = Configuration()
c.verify_ssl = False
Configuration.set_default(c)
return c
def _get_kubernetesclient_batch(self):
if not self._kube_client_batch:
@ -190,6 +198,9 @@ class KubeOperator(object):
self._kube_client_custom_objects = client.CustomObjectsApi()
return self._kube_client_custom_objects
def kube_get_kubernetes_config(self):
return self._load_kube_config()
def kube_patch_node(self, name, body):
try:
api_response = self._get_kubernetesclient_core().patch_node(name, body)
@ -530,6 +541,30 @@ class KubeOperator(object):
% (namespace, e))
raise
def kube_get_service_account(self, name, namespace):
c = self._get_kubernetesclient_core()
try:
return c.read_namespaced_service_account(name, namespace)
except ApiException as e:
if e.status == httplib.NOT_FOUND:
return None
else:
LOG.error("Failed to get ServiceAccount %s under "
"Namespace %s: %s" % (name, namespace, e.body))
raise
except Exception as e:
LOG.error("Kubernetes exception in kube_get_service_account: %s" % e)
raise
def kube_get_service_account_token(self, name, namespace):
sa = self.kube_get_service_account(name, namespace)
if not sa:
# ServiceAccount does not exist, no token available
return None
secret = self.kube_get_secret(sa.secrets[0].name, namespace)
return secret.data.get('token')
def kube_get_control_plane_pod_ready_status(self):
"""Returns the ready status of the control plane pods."""
c = self._get_kubernetesclient_core()

View File

@ -0,0 +1,253 @@
#
# Copyright (c) 2021 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
"""
Tests for the API /kube_clusters/ methods.
"""
import copy
import mock
import tempfile
from kubernetes.client import Configuration
from sysinv.common import utils
from sysinv.common import kubernetes
from sysinv.tests.api import base
from sysinv.tests.db import base as dbbase
FAKE_CA_CERT = (
"-----BEGIN CERTIFICATE-----\n"
"MIIE7TCCAtWgAwIBAgIDAODHMA0GCSqGSIb3DQEBCwUAMB8xEDAOBgNVBAoMB0V0\n"
"Y2QgQ0ExCzAJBgNVBAMMAmNhMB4XDTIxMDIyMDE0MDcxOFoXDTMxMDIxODE0MDcx\n"
"OFowHzEQMA4GA1UECgwHRXRjZCBDQTELMAkGA1UEAwwCY2EwggIiMA0GCSqGSIb3\n"
"DQEBAQUAA4ICDwAwggIKAoICAQDL+NVHmb69Dl+D9M1g0eQa3uq4nThcwQ+gimbU\n"
"GcBPBJmfDmrKvIxLde8RZ+tR+N77mT76qbHtS2KlYgIALJV0ZhujFzmytQ3r0T54\n"
"bzrSfczfvQ5zx8kGc6KWmvi86VeuX26tEuN4Kklg1Lljrl9RJ3JJ7ck6Q92wVO1U\n"
"kQmIWUZWclEOSBQbEj1p5CDZIRxf6ZIE57f1FoFzk9MaVVAOwZKgPiN5XSsHRmRT\n"
"3igMP/X/seZQ7q8+Bg1pGxOwCGhxnxHGTzKXTE5VNXnLH2SYfm/RBrn3FxTE2Rp7\n"
"hAjEnt+XZxw4Eju8oNahnIGVb0JWy1gJ6RMgtyQWs1cky7DfDQiF8RmciLuTx4Gy\n"
"81W5RSelQDqrIQueBJrHBNF1nR7F9lu2+51ZgWeqdqLEwFzyjOFDem6vpskzMO75\n"
"EwZMJlWi3ez/xdkYKqg38QKZRfRiIeoi8BbV4wnSXqyxBJ/DZ1NAwbumbP/GRU7j\n"
"m6RS5wlMznwg55pXpiWLDFmJ7YFu+LU1WxYicE4qjPMYBn0OcMR4b8n/f5vGLd9O\n"
"ZPzTLIt5B+9NqMpqoFePsS4anFFJvvhVEK4WwEFsmdii76bv7pYCBftlsEK7o1Mc\n"
"6YFGoTpNZyDA9BFTp0CB7WArQDxQHikDLQzwpqwVZOjcJQN7Rzf0X4bHtW5NdgMJ\n"
"NIhDCwIDAQABozIwMDANBgNVHREEBjAEggJjYTALBgNVHQ8EBAMCAoQwEgYDVR0T\n"
"AQH/BAgwBgEB/wIBATANBgkqhkiG9w0BAQsFAAOCAgEAuKu0XyKN9ZKHT1KvP1uc\n"
"tzChXkeqQ47ciUbUvM56XvcaoB/4/VMax/2RuNBjC7XqXM+FkwZMBHnmU+hxcZ/Z\n"
"evbF46y/puGqgDFgRKb4iSfo9JxnU5wsMX5lPwlYEuRmbJjyFvDNZdseNtH3Ws/4\n"
"iQUGaiHl9HfOePQlb9QDprbRu/Dp6iE6Wai5fuURIXtB9PP5OD2q333vpYolmXXa\n"
"e9ybwYD8E1X8MLQV0Irh/dJ+5T/eqtWUrZ2YhpCuAawGU35L/1ZqDT4rXW40BcoP\n"
"cYSSr4ryWKGynYGjrnu2EnxHkYqIsgMDS/Jq8CjrZLpZ4E4TagXoZhIOa5Y3Yq9p\n"
"yEH4zskY30BUoP7h8Bp7hZIIJ1LyI1F04mukJdHdVH89mhIkU5RuIOJoiBPOMkQw\n"
"GmRIG8IYQMFxplwtebQrQpE6lnnIE2EdUxxqtpqAqPxnRf6LQg/gtjlGRotKiI9D\n"
"6ypovjCQi49X4WBjiBFnrgma9MsFL2ZOJPX6XpGZ6jqBTAtVMcdb+hsZQMm8/M2Z\n"
"QITmxBO+A1hkXGjofbo145omm5qFcWmbvvrnviv3iShEsCoIFpFnGf8RvWwNapeN\n"
"W4WzyAwY1pQs7Er2KEixiPG7BGaC7KUD3l1kB/IeF0rpnO8rmW/Hq23eLRqtk7mF\n"
"8M4zFA2c4PFD35Vu9ERU20E=\n"
"-----END CERTIFICATE-----\n")
FAKE_CLIENT_CERT = (
"-----BEGIN CERTIFICATE-----\n"
"MIID/DCCAeSgAwIBAgIIRdk0W8Cf6RkwDQYJKoZIhvcNAQELBQAwHzEQMA4GA1UE\n"
"CgwHRXRjZCBDQTELMAkGA1UEAwwCY2EwHhcNMjEwMjIwMTQwNzE4WhcNMjIwMjIw\n"
"MTQyMzU4WjA0MRcwFQYDVQQKEw5zeXN0ZW06bWFzdGVyczEZMBcGA1UEAxMQa3Vi\n"
"ZXJuZXRlcy1hZG1pbjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJ5x\n"
"iFIxCMDtmbbkmuxPidugAOhcq8KQ7W7xiFKxzzyxEzOyoK3zsyL+vaMKSrq19Tc+\n"
"bFcdm/zLPPS4RtjmUK5VP0Z5dA6a06PHlXJ/CMlZIHIQJolGYfYDg4Ky7oYFQ/KP\n"
"4rtVGvyV7mSdhBdKIelgZ/45zyy10leq2oAWChi9P7kNX2pbwBxgLu1yCuz0f9d1\n"
"hyx+hm11RDpUJKsbqNzgvP9nJUiSIbfcNAv7ut5RcC/mpITBdyiCnMMs6DvpC3ao\n"
"xKTks2XWpxgK3Ay1LYjkpaqtMuYK3dGps0Au5b/fSUlJqfzbD0I6wmZYlZK/x/E9\n"
"+aAALAceGudvBovWxW0CAwEAAaMnMCUwDgYDVR0PAQH/BAQDAgWgMBMGA1UdJQQM\n"
"MAoGCCsGAQUFBwMCMA0GCSqGSIb3DQEBCwUAA4ICAQCkVUfWJ/sHdKN++Uaw+wu2\n"
"GFtBnk50btuKcB9vf9fGJK7LHWI5xSMs0kiCXHi3tpa/LvWj0FFWZZ/2fwaV/hfM\n"
"VJUk0pF2Xjp9IuzFcI/SJWROX/TmZUxFSUL1LMaojdbLqPmIcBRJE9Kd/f0+hmtt\n"
"2v9o8E52F8pTSG98dGAvWBfsaktiUos2FbYAJE2UKX5dTnLBLJws55xHx5isHkb5\n"
"I8wb+NbSlKq2Hs4oR0SAjCo+2P+Ej3YblwitPkhV7AkzljHdyKr/f+QT29qgYrW2\n"
"qi7Ftg/9fBsiiCLjLp+DJrfJQR1YnTVuhv8PCTO46IFzT3zxVe/A3EnKj/kps2y8\n"
"qeMeDHvxEACoSXQoE2yZVyCKqp1FEjawXeAS3QAicFdoSAjhC5FSTnRs28UE6tXB\n"
"VqWUUG0FY2/zwswAfIktClJ492utO0HBJt76HcRfR1699Qmfx6fLFKQUDM6fxJk6\n"
"79QI3S2s3eiCwiPtHOUAz7LC5KV6c75Yq+LABY9eN5K4EI6fuD8cEhfDj3iBb3bB\n"
"0jJp0bFsCpD90Nrx253XiVesHiKhLlvnNUVuAylDcvwt8xVv+uuBl4kpVv4kkyT/\n"
"ApqqvGKcUwQp9jIdY9nSZ/SZRW8QFzf404UVeiH+Ruu6+CCqh2PLAtDnSCPVRt1e\n"
"O+hShAzOqQGF72F6XYlx/g==\n"
"-----END CERTIFICATE-----\n")
FAKE_CLIENT_KEY = (
"-----BEGIN RSA PRIVATE KEY-----\n"
"MIIEpAIBAAKCAQEAnnGIUjEIwO2ZtuSa7E+J26AA6FyrwpDtbvGIUrHPPLETM7Kg\n"
"rfOzIv69owpKurX1Nz5sVx2b/Ms89LhG2OZQrlU/Rnl0DprTo8eVcn8IyVkgchAm\n"
"iUZh9gODgrLuhgVD8o/iu1Ua/JXuZJ2EF0oh6WBn/jnPLLXSV6ragBYKGL0/uQ1f\n"
"alvAHGAu7XIK7PR/13WHLH6GbXVEOlQkqxuo3OC8/2clSJIht9w0C/u63lFwL+ak\n"
"hMF3KIKcwyzoO+kLdqjEpOSzZdanGArcDLUtiOSlqq0y5grd0amzQC7lv99JSUmp\n"
"/NsPQjrCZliVkr/H8T35oAAsBx4a528Gi9bFbQIDAQABAoIBAQCJzUZ57ammWj/x\n"
"oJvZYUgOGvgPH+JG41ONxUYCXiFWsM95jCdRg33Otu3qKl5aSz0Noh4KGnd7gqvu\n"
"T4NWy+Fp7jyNJ763oRLnBAPHxBK5Q+oDKmbJx8wVcnLjronjSBsTkO7qbRd+jUv8\n"
"eD7VHqWl2zI3GsJEKZLaqn9FHWYEot2s17obd//4lJPcBg6kGhHDGkJFm7xvVELa\n"
"VXCIN1E9bAoIgv3pie+O53FH0YoXptvYG4F+ffHGk8/cbdcBJ4oLJqF2mJiwuBbf\n"
"GYa5T/rIoPkrnc+kmGcePC6pPjPxttHvyaWIDQZj4Jcy4oz6tzFUF0oEZ2/JfMBt\n"
"Il13gqylAoGBAMU/oaxXHM//NRQqMlL9R8LYLcnze2rnqt+T0ORUZQGETSSXaTxv\n"
"I4T2wyy9yB583RDVJNXp4T3Js2XnweNj8pRRsCjxY1lkpSOaLVqAw/1HwK1DOSEG\n"
"EqW8s37YOPZWGAYIhpfEbD5y960JUjVsuW71w/5cDWkoi1eyeFVbuXg7AoGBAM2i\n"
"+0A6IrZsy/oIJoF8xmOEH78zEMv8W6Qmfb24N0hdcaFJnM48aF50wk8+/YgMNI7J\n"
"kKR7JJAIQmIFn8tYji9zeeRny4iAclRb6ecglCspvxLzF7tci3ae1snaOFs2wz6b\n"
"MkLSfb4nNf2u3dsJ2Z0tU8Tb7pxCDH/yEjCRA4Z3AoGAM/T58jqUFVnlMmWXEfMz\n"
"puhoz0x6kwNpKDF4kdyFKqwd4eicSNYBpjGV4cAv6Y/8b0WlyU8tDKiHv+0XTn1y\n"
"VY1a+L307IQtV75x+ef3OE1hPIJ7lu5RlSSqp1vvTTwKYfR2950+4ghIo2TUKcx0\n"
"3/yO3v6CbdPHOJeDSQC7TycCgYEAq61XyaU/ecGXAaVwUEaVclvKDVxat5J2B7NC\n"
"4vM65CVvSlIkoWF5WPJtjq9uBvj5oAPTyB4uxji/Awri/2dtPVxQ9UlaeRmTWa5q\n"
"ttVSHj76EJ32wCthG6U8eMTArBYqJsh2y6bj567gumwVOFse3MQM3ZsnuDjEKsU0\n"
"Pmuy370CgYAULotjgbSNBZcJu2a2urX+vtjPwpGSsiKYdVHBvspcwWcu8YaaeAde\n"
"71781PJbFV7v45nT2thc+w9IYemXATH/cOO+JVUMYqZY0c+AOa8bvjnMY5Z6cS6Y\n"
"WJC6NHVmvvFb1YhXjQz2GA9GGBmx9+5/vaPp4aPp+VMfdt9MkEV/NQ==\n"
"-----END RSA PRIVATE KEY-----\n")
FAKE_TOKEN = (
"ZXlKaGJHY2lPaUpTVXpJMU5pSXNJbXRwWkNJNklpMXpXRFZyUkVreFZqQTVUVTVU"
"UmtOSFVuQTBVSE5PVTNWdlJFaG1RM1ozT1VGMU1UbGZZemhtVFZraWZRLmV5SnBj"
"M01pT2lKcmRXSmxjbTVsZEdWekwzTmxjblpwWTJWaFkyTnZkVzUwSWl3aWEzVmla"
"WEp1WlhSbGN5NXBieTl6WlhKMmFXTmxZV05qYjNWdWRDOXVZVzFsYzNCaFkyVWlP"
"aUpyZFdKbExYTjVjM1JsYlNJc0ltdDFZbVZ5Ym1WMFpYTXVhVzh2YzJWeWRtbGpa"
"V0ZqWTI5MWJuUXZjMlZqY21WMExtNWhiV1VpT2lKcmRXSmxjbTVsZEdWekxXRmti"
"V2x1TFhSdmEyVnVMVFIyYzNCdElpd2lhM1ZpWlhKdVpYUmxjeTVwYnk5elpYSjJh"
"V05sWVdOamIzVnVkQzl6WlhKMmFXTmxMV0ZqWTI5MWJuUXVibUZ0WlNJNkltdDFZ"
"bVZ5Ym1WMFpYTXRZV1J0YVc0aUxDSnJkV0psY201bGRHVnpMbWx2TDNObGNuWnBZ"
"MlZoWTJOdmRXNTBMM05sY25acFkyVXRZV05qYjNWdWRDNTFhV1FpT2lJMFlURm1a"
"VEpqTlMweU5qQTJMVFJoWWpRdFlqTXlNUzB5TjJWak1HRXdZVFkyTnpnaUxDSnpk"
"V0lpT2lKemVYTjBaVzA2YzJWeWRtbGpaV0ZqWTI5MWJuUTZhM1ZpWlMxemVYTjBa"
"VzA2YTNWaVpYSnVaWFJsY3kxaFpHMXBiaUo5LlhyRU5hNXI5SXRwOGJjM25aMVZo"
"ZkJlUEFaQ1l2dU5oUVFLYVhNWXlLVjZmQXFiSENIQi1kVnJUYXcxbWs5YXdIQmVz"
"MXhKUFliVHdzU2dacTZkdFlLYjZuY2RGUUpCYjM2aGJ0NnJ4WnJsZlNYRzFVS2xy"
"MlQ4ZW1KaFVCV3hFSzVXazRLU1ZobnVBcmJDLUU3MDNTd0hVdEU2UUhDWkRGTWFk"
"QUoyajJDNmo2RktoLXIwUWpfQ1I4TzBVUTF4c0I0YW9ZS05rUGUxeFJZSVZKUTFW"
"TjlFdkFaa3lUUFhORDhpUV9hQVFuSlBfUFlCS09OLTAyTnZOY3llVjZ1LWNzdzI3"
"NVAyYXJIeGdLLXZrMG5Ec1FkTkR5S3hBY2t3Skc3bkVyVmJkNVJoY2JiN2gwX2Jx"
"dmt4QnJmaEJ5STE4c3k1WFdQTGE4cThIVVE3d092RlpXUQ==")
FAKE_CLUSTER_VERSION = "v1.18.1"
FAKE_API_ENDPOINT = "https://localhost:6443"
FAKE_CLUSTER = {
"cluster_name": kubernetes.KUBERNETES_CLUSTER_DEFAULT,
"cluster_version": FAKE_CLUSTER_VERSION,
"cluster_api_endpoint": FAKE_API_ENDPOINT,
"cluster_ca_cert": FAKE_CA_CERT,
"admin_client_cert": FAKE_CLIENT_CERT,
"admin_client_key": FAKE_CLIENT_KEY,
"admin_user": kubernetes.KUBERNETES_ADMIN_USER,
"admin_token": FAKE_TOKEN
}
class TestKubeCluster(base.FunctionalTest):
def setUp(self):
super(TestKubeCluster, self).setUp()
self.ssl_ca_file = self._create_temp_file(FAKE_CA_CERT)
self.cert_file = self._create_temp_file(FAKE_CLIENT_CERT)
self.key_file = self._create_temp_file(FAKE_CLIENT_KEY)
def mock_kube_get_kubernetes_version(obj):
return FAKE_CLUSTER_VERSION
self.mocked_kube_get_kubernetes_version = mock.patch(
'sysinv.common.kubernetes.KubeOperator.kube_get_kubernetes_version',
mock_kube_get_kubernetes_version)
self.mocked_kube_get_kubernetes_version.start()
def mock_kube_get_kubernetes_config(obj):
config = Configuration()
config.host = FAKE_API_ENDPOINT
config.ssl_ca_cert = self.ssl_ca_file.name
config.cert_file = self.cert_file.name
config.key_file = self.key_file.name
return config
self.mocked_kube_get_kubernetes_config = mock.patch(
'sysinv.common.kubernetes.KubeOperator.kube_get_kubernetes_config',
mock_kube_get_kubernetes_config)
self.mocked_kube_get_kubernetes_config.start()
def mock_kube_get_service_account_token(obj, name, namespace):
return FAKE_TOKEN
self.mocked_kube_get_service_account_token = mock.patch(
'sysinv.common.kubernetes.KubeOperator.kube_get_service_account_token',
mock_kube_get_service_account_token)
self.mocked_kube_get_service_account_token.start()
def tearDown(self):
super(TestKubeCluster, self).tearDown()
self.ssl_ca_file.close()
self.cert_file.close()
self.key_file.close()
self.mocked_kube_get_kubernetes_version.stop()
self.mocked_kube_get_kubernetes_config.stop()
self.mocked_kube_get_service_account_token.stop()
def _get_cluster_api_endpoint(self):
endpoint_host = utils.format_url_address(self.oam_subnet[2])
return "https://{}:6443".format(endpoint_host)
def _create_temp_file(self, content):
fp = tempfile.NamedTemporaryFile()
fp.write(str.encode(content))
fp.seek(0)
return fp
class TestListKubeCluster(TestKubeCluster,
dbbase.ControllerHostTestCase):
def test_list_cluster(self):
fake_cluster = copy.deepcopy(FAKE_CLUSTER)
# update the expected cluster_api_endpoint
fake_cluster['cluster_api_endpoint'] = \
self._get_cluster_api_endpoint()
fake_clusters = [fake_cluster]
result = self.get_json('/kube_clusters')
self.assertEqual(len(fake_clusters), len(result['kube_clusters']))
for index, cluster in enumerate(result['kube_clusters']):
fake_cluster = fake_clusters[index]
for key, value in fake_cluster.items():
self.assertEqual(cluster[key], value)
class TestGetKubeCluster(TestKubeCluster,
dbbase.ControllerHostTestCase):
def test_get_cluster(self):
fake_cluster = copy.deepcopy(FAKE_CLUSTER)
# update the expected cluster_api_endpoint
fake_cluster['cluster_api_endpoint'] = \
self._get_cluster_api_endpoint()
result = self.get_json('/kube_clusters/%s' %
kubernetes.KUBERNETES_CLUSTER_DEFAULT)
for key, value in fake_cluster.items():
self.assertEqual(result[key], value)
class TestIpv4ListKubeCluster(TestListKubeCluster):
pass
class TestIpv6ListKubeCluster(dbbase.BaseIPv6Mixin,
TestListKubeCluster):
pass
class TestIpv4GetKubeCluster(TestGetKubeCluster):
pass
class TestIpv6GetKubeCluster(dbbase.BaseIPv6Mixin,
TestGetKubeCluster):
pass

View File

@ -71,6 +71,8 @@ FAKE_POD_STATUS = kubernetes.client.V1PodStatus(
],
)
FAKE_SERVICE_ACCOUNT_TOKEN = 'c3VwZXJzZWNyZXR0b2tlbgo='
def mock_get_kube_versions():
return FAKE_KUBE_VERSIONS
@ -622,6 +624,30 @@ class TestKubeOperator(base.TestCase):
namespace="kube-system"),
)
self.service_account_result = kubernetes.client.V1ServiceAccount(
api_version="v1",
kind="ServiceAccount",
metadata=kubernetes.client.V1ObjectMeta(
name="test-service-account-1",
namespace="kube-system"),
secrets=[kubernetes.client.V1ObjectReference(
kind="Secret",
namespace="kube-system",
name="test-service-account-secret-1"
)]
)
self.service_account_token_result = kubernetes.client.V1Secret(
api_version="v1",
kind="Secret",
metadata=kubernetes.client.V1ObjectMeta(
name="test-service-account-secret-1",
namespace="kube-system"),
data={
'token': FAKE_SERVICE_ACCOUNT_TOKEN
}
)
def setUp(self):
super(TestKubeOperator, self).setUp()
@ -671,6 +697,20 @@ class TestKubeOperator(base.TestCase):
mock_read_namespaced_config_map)
self.mocked_read_namespaced_config_map.start()
def mock_read_namespaced_service_account(obj, name, namespace):
return self.read_namespaced_service_account_result
self.mocked_read_namespaced_service_account = mock.patch(
'kubernetes.client.CoreV1Api.read_namespaced_service_account',
mock_read_namespaced_service_account)
self.mocked_read_namespaced_service_account.start()
def mock_read_namespaced_secret(obj, name, namespace):
return self.read_namespaced_secret_result
self.mocked_read_namespaced_secret = mock.patch(
'kubernetes.client.CoreV1Api.read_namespaced_secret',
mock_read_namespaced_secret)
self.mocked_read_namespaced_secret.start()
self.kube_operator = kube.KubeOperator()
def tearDown(self):
@ -680,6 +720,8 @@ class TestKubeOperator(base.TestCase):
self.mocked_list_pod_for_all_namespaces.stop()
self.mocked_list_node.stop()
self.mocked_read_namespaced_config_map.stop()
self.mocked_read_namespaced_service_account.stop()
self.mocked_read_namespaced_secret.stop()
def test_kube_get_image_by_pod_name(self):
@ -891,6 +933,27 @@ class TestKubeOperator(base.TestCase):
result = self.kube_operator.kube_get_kubernetes_version()
assert result is None
def test_kube_get_service_account_token(self):
self.read_namespaced_service_account_result = \
self.service_account_result
self.read_namespaced_secret_result = \
self.service_account_token_result
result = self.kube_operator.kube_get_service_account_token(
'test-service-account-1', kube.NAMESPACE_KUBE_SYSTEM)
self.assertEqual(result, FAKE_SERVICE_ACCOUNT_TOKEN)
def test_kube_get_service_account_token_not_found(self):
self.read_namespaced_service_account_result = None
self.read_namespaced_secret_result = None
result = self.kube_operator.kube_get_service_account_token(
'test-service-account-1', kube.NAMESPACE_KUBE_SYSTEM)
self.assertEqual(result, None)
class TestKubernetesUtilities(base.TestCase):
def test_is_kube_version_supported(self):