Implements k8s resource creation/updating with data
Now kube_utils.py supports creation/updating from k8s resource data. User will be able to updload pod/service/replication_controller data to create or update resource. Change-Id: I98f7ab2eab7ac9e68c4e4a313a61f7d39b7596b2
This commit is contained in:
parent
be136bbd8e
commit
9ff6b93e08
|
@ -12,12 +12,74 @@
|
||||||
|
|
||||||
"""Magnum Kubernetes RPC handler."""
|
"""Magnum Kubernetes RPC handler."""
|
||||||
|
|
||||||
|
import tempfile
|
||||||
|
|
||||||
from magnum.openstack.common import log as logging
|
from magnum.openstack.common import log as logging
|
||||||
from magnum.openstack.common import utils
|
from magnum.openstack.common import utils
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def _extract_resource_type(resource):
|
||||||
|
return resource.__class__.__name__.lower()
|
||||||
|
|
||||||
|
|
||||||
|
def _extract_resource_data(resource):
|
||||||
|
resource_type = _extract_resource_type(resource)
|
||||||
|
data_attribute = "%s_data" % resource_type
|
||||||
|
return getattr(resource, data_attribute, None)
|
||||||
|
|
||||||
|
|
||||||
|
def _extract_resource_definition_url(resource):
|
||||||
|
resource_type = _extract_resource_type(resource)
|
||||||
|
definition_url_attribute = "%s_definition_url" % resource_type
|
||||||
|
return getattr(resource, definition_url_attribute, None)
|
||||||
|
|
||||||
|
|
||||||
|
def _k8s_create(master_address, resource):
|
||||||
|
data = _extract_resource_data(resource)
|
||||||
|
definition_url = _extract_resource_definition_url(resource)
|
||||||
|
if data is not None:
|
||||||
|
return _k8s_create_with_data(master_address, data)
|
||||||
|
else:
|
||||||
|
return _k8s_create_with_path(master_address, definition_url)
|
||||||
|
|
||||||
|
|
||||||
|
def _k8s_create_with_path(master_address, resource_file):
|
||||||
|
return utils.trycmd('kubectl', 'create',
|
||||||
|
'-s', master_address,
|
||||||
|
'-f', resource_file)
|
||||||
|
|
||||||
|
|
||||||
|
def _k8s_create_with_data(master_address, resource_data):
|
||||||
|
with tempfile.NamedTemporaryFile() as f:
|
||||||
|
f.write(resource_data)
|
||||||
|
f.flush()
|
||||||
|
return _k8s_create_with_path(master_address, f.name)
|
||||||
|
|
||||||
|
|
||||||
|
def _k8s_update(master_address, resource):
|
||||||
|
data = _extract_resource_data(resource)
|
||||||
|
definition_url = _extract_resource_definition_url(resource)
|
||||||
|
if data is not None:
|
||||||
|
return _k8s_update_with_data(master_address, data)
|
||||||
|
else:
|
||||||
|
return _k8s_update_with_path(master_address, definition_url)
|
||||||
|
|
||||||
|
|
||||||
|
def _k8s_update_with_path(master_address, resource_file):
|
||||||
|
return utils.trycmd('kubectl', 'update',
|
||||||
|
'-s', master_address,
|
||||||
|
'-f', resource_file)
|
||||||
|
|
||||||
|
|
||||||
|
def _k8s_update_with_data(master_address, resource_data):
|
||||||
|
with tempfile.NamedTemporaryFile() as f:
|
||||||
|
f.write(resource_data)
|
||||||
|
f.flush()
|
||||||
|
return _k8s_update_with_path(master_address, f.name)
|
||||||
|
|
||||||
|
|
||||||
class KubeClient(object):
|
class KubeClient(object):
|
||||||
"""These are the backend operations. They are executed by the backend
|
"""These are the backend operations. They are executed by the backend
|
||||||
service. API calls via AMQP (within the ReST API) trigger the
|
service. API calls via AMQP (within the ReST API) trigger the
|
||||||
|
@ -33,14 +95,8 @@ class KubeClient(object):
|
||||||
def service_create(self, master_address, service):
|
def service_create(self, master_address, service):
|
||||||
LOG.debug("service_create with contents %s" % service)
|
LOG.debug("service_create with contents %s" % service)
|
||||||
try:
|
try:
|
||||||
if service.service_definition_url:
|
out, err = _k8s_create(master_address, service)
|
||||||
out, err = utils.trycmd('kubectl', 'create',
|
|
||||||
'-s', master_address,
|
|
||||||
'-f', service.service_definition_url)
|
|
||||||
else:
|
|
||||||
# TODO(jay-lau-513) Translate the contents to a json stdin
|
|
||||||
out, err = utils.trycmd('echo service | kubectl', 'create',
|
|
||||||
'-f', '-')
|
|
||||||
if err:
|
if err:
|
||||||
return False
|
return False
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
@ -52,15 +108,8 @@ class KubeClient(object):
|
||||||
def service_update(self, master_address, service):
|
def service_update(self, master_address, service):
|
||||||
LOG.debug("service_update with contents %s" % service)
|
LOG.debug("service_update with contents %s" % service)
|
||||||
try:
|
try:
|
||||||
if service.service_definition_url:
|
out, err = _k8s_update(master_address, service)
|
||||||
out, err = utils.trycmd('kubectl', 'update',
|
|
||||||
'-s', master_address,
|
|
||||||
'-f', service.service_definition_url)
|
|
||||||
else:
|
|
||||||
# TODO(jay-lau-513) Translate the contents to a json stdin
|
|
||||||
out, err = utils.trycmd('echo service | kubectl', 'update',
|
|
||||||
'-s', master_address,
|
|
||||||
'-f', '-')
|
|
||||||
if err:
|
if err:
|
||||||
return False
|
return False
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
@ -117,43 +166,29 @@ class KubeClient(object):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# Pod Operations
|
# Pod Operations
|
||||||
def pod_create(self, master_address, contents):
|
def pod_create(self, master_address, pod):
|
||||||
LOG.debug("pod_create contents %s" % contents)
|
LOG.debug("pod_create contents %s" % pod)
|
||||||
try:
|
try:
|
||||||
if contents.pod_definition_url:
|
out, err = _k8s_create(master_address, pod)
|
||||||
out, err = utils.trycmd('kubectl', 'create',
|
|
||||||
'-s', master_address,
|
|
||||||
'-f', contents.pod_definition_url)
|
|
||||||
else:
|
|
||||||
# TODO(jay-lau-513) Translate the contents to a json stdin
|
|
||||||
out, err = utils.trycmd('echo contents | kubectl', 'create',
|
|
||||||
'-s', master_address,
|
|
||||||
'-f', '-')
|
|
||||||
if err:
|
if err:
|
||||||
return False
|
return False
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
LOG.error("Couldn't create pod with contents %s due to error %s"
|
LOG.error("Couldn't create pod with contents %s due to error %s"
|
||||||
% (contents, e))
|
% (pod, e))
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def pod_update(self, master_address, contents):
|
def pod_update(self, master_address, pod):
|
||||||
LOG.debug("pod_update contents %s" % contents)
|
LOG.debug("pod_update contents %s" % pod)
|
||||||
try:
|
try:
|
||||||
if contents.pod_definition_url:
|
out, err = _k8s_update(master_address, pod)
|
||||||
out, err = utils.trycmd('kubectl', 'update',
|
|
||||||
'-s', master_address,
|
|
||||||
'-f', contents.pod_definition_url)
|
|
||||||
else:
|
|
||||||
# TODO(jay-lau-513) Translate the contents to a json stdin
|
|
||||||
out, err = utils.trycmd('echo contents | kubectl', 'update',
|
|
||||||
'-s', master_address,
|
|
||||||
'-f', '-')
|
|
||||||
if err:
|
if err:
|
||||||
return False
|
return False
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
LOG.error("Couldn't update pod with contents %s due to error %s"
|
LOG.error("Couldn't update pod with contents %s due to error %s"
|
||||||
% (contents, e))
|
% (pod, e))
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
@ -202,43 +237,29 @@ class KubeClient(object):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# Replication Controller Operations
|
# Replication Controller Operations
|
||||||
def rc_create(self, master_address, contents):
|
def rc_create(self, master_address, rc):
|
||||||
LOG.debug("rc_create contents %s" % contents)
|
LOG.debug("rc_create contents %s" % rc)
|
||||||
try:
|
try:
|
||||||
if contents.rc_definition_url:
|
out, err = _k8s_create(master_address, rc)
|
||||||
out, err = utils.trycmd('kubectl', 'create',
|
|
||||||
'-s', master_address,
|
|
||||||
'-f', contents.rc_definition_url)
|
|
||||||
else:
|
|
||||||
# TODO(jay-lau-513) Translate the contents to a json stdin
|
|
||||||
out, err = utils.trycmd('echo contents | kubectl', 'create',
|
|
||||||
'-s', master_address,
|
|
||||||
'-f', '-')
|
|
||||||
if err:
|
if err:
|
||||||
return False
|
return False
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
LOG.error("Couldn't create rc with contents %s due to error %s"
|
LOG.error("Couldn't create rc with contents %s due to error %s"
|
||||||
% (contents, e))
|
% (rc, e))
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def rc_update(self, master_address, contents):
|
def rc_update(self, master_address, rc):
|
||||||
LOG.debug("rc_update contents %s" % contents)
|
LOG.debug("rc_update contents %s" % rc)
|
||||||
try:
|
try:
|
||||||
if contents.rc_definition_url:
|
out, err = _k8s_update(master_address, rc)
|
||||||
out, err = utils.trycmd('kubectl', 'update',
|
|
||||||
'-s', master_address,
|
|
||||||
'-f', contents.rc_definition_url)
|
|
||||||
else:
|
|
||||||
# TODO(jay-lau-513) Translate the contents to a json stdin
|
|
||||||
out, err = utils.trycmd('echo contents | kubectl', 'update',
|
|
||||||
'-s', master_address,
|
|
||||||
'-f', '-')
|
|
||||||
if err:
|
if err:
|
||||||
return False
|
return False
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
LOG.error("Couldn't update rc with contents %s due to error %s"
|
LOG.error("Couldn't update rc with contents %s due to error %s"
|
||||||
% (contents, e))
|
% (rc, e))
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,201 @@
|
||||||
|
# Copyright 2014 NEC Corporation. All rights reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
from magnum.conductor.handlers.common import kube_utils
|
||||||
|
from magnum import objects
|
||||||
|
from magnum.tests import base
|
||||||
|
|
||||||
|
import mock
|
||||||
|
from mock import patch
|
||||||
|
|
||||||
|
|
||||||
|
class TestKubeUtils(base.BaseTestCase):
|
||||||
|
def setUp(self):
|
||||||
|
super(TestKubeUtils, self).setUp()
|
||||||
|
|
||||||
|
def test_extract_resource_type(self):
|
||||||
|
expected_resource_type = 'bay'
|
||||||
|
|
||||||
|
bay = objects.Bay({})
|
||||||
|
actual_type = kube_utils._extract_resource_type(bay)
|
||||||
|
self.assertEqual(expected_resource_type, actual_type)
|
||||||
|
|
||||||
|
@patch('magnum.conductor.handlers.common.kube_utils.'
|
||||||
|
'_extract_resource_type')
|
||||||
|
def test_extract_resource_data_with_data(self,
|
||||||
|
mock_extract_resource_type):
|
||||||
|
expected_data = 'expected_data'
|
||||||
|
|
||||||
|
mock_extract_resource_type.return_value = 'mock_type'
|
||||||
|
mock_resource = mock.MagicMock()
|
||||||
|
mock_resource.mock_type_data = expected_data
|
||||||
|
actual_data = kube_utils._extract_resource_data(mock_resource)
|
||||||
|
self.assertEqual(expected_data, actual_data)
|
||||||
|
|
||||||
|
@patch('magnum.conductor.handlers.common.kube_utils.'
|
||||||
|
'_extract_resource_type')
|
||||||
|
def test_extract_resource_definition_url(self,
|
||||||
|
mock_extract_resource_type):
|
||||||
|
expected_data = 'expected_url'
|
||||||
|
|
||||||
|
mock_extract_resource_type.return_value = 'mock_type'
|
||||||
|
mock_resource = mock.MagicMock()
|
||||||
|
mock_resource.mock_type_definition_url = expected_data
|
||||||
|
actual_data = kube_utils._extract_resource_definition_url(
|
||||||
|
mock_resource)
|
||||||
|
self.assertEqual(expected_data, actual_data)
|
||||||
|
|
||||||
|
@patch('magnum.conductor.handlers.common.kube_utils._k8s_create_with_data')
|
||||||
|
@patch('magnum.conductor.handlers.common.kube_utils.'
|
||||||
|
'_extract_resource_data')
|
||||||
|
@patch('magnum.conductor.handlers.common.kube_utils.'
|
||||||
|
'_extract_resource_definition_url')
|
||||||
|
def test_k8s_create_data(self,
|
||||||
|
mock_definition_url,
|
||||||
|
mock_data,
|
||||||
|
mock_create_with_data):
|
||||||
|
expected_data = 'data'
|
||||||
|
master_address = 'master_address'
|
||||||
|
mock_data.return_value = expected_data
|
||||||
|
mock_definition_url.return_value = None
|
||||||
|
mock_resource = mock.MagicMock()
|
||||||
|
|
||||||
|
kube_utils._k8s_create(master_address, mock_resource)
|
||||||
|
mock_create_with_data.assert_called_once_with(master_address,
|
||||||
|
expected_data)
|
||||||
|
|
||||||
|
@patch('magnum.conductor.handlers.common.kube_utils._k8s_create_with_path')
|
||||||
|
@patch('magnum.conductor.handlers.common.kube_utils.'
|
||||||
|
'_extract_resource_data')
|
||||||
|
@patch('magnum.conductor.handlers.common.kube_utils.'
|
||||||
|
'_extract_resource_definition_url')
|
||||||
|
def test_k8s_create_url(self,
|
||||||
|
mock_definition_url,
|
||||||
|
mock_data,
|
||||||
|
mock_create_with_path):
|
||||||
|
expected_url = 'url'
|
||||||
|
master_address = 'master_address'
|
||||||
|
mock_data.return_value = None
|
||||||
|
mock_definition_url.return_value = expected_url
|
||||||
|
mock_resource = mock.MagicMock()
|
||||||
|
|
||||||
|
kube_utils._k8s_create(master_address, mock_resource)
|
||||||
|
mock_create_with_path.assert_called_once_with(master_address,
|
||||||
|
expected_url)
|
||||||
|
|
||||||
|
@patch('magnum.openstack.common.utils.trycmd')
|
||||||
|
def test_k8s_create_with_path(self, mock_trycmd):
|
||||||
|
expected_master_address = 'master_address'
|
||||||
|
expected_pod_file = 'pod_file'
|
||||||
|
expected_command = [
|
||||||
|
'kubectl', 'create',
|
||||||
|
'-s', expected_master_address,
|
||||||
|
'-f', expected_pod_file
|
||||||
|
]
|
||||||
|
|
||||||
|
kube_utils._k8s_create_with_path(expected_master_address,
|
||||||
|
expected_pod_file)
|
||||||
|
mock_trycmd.assert_called_once_with(*expected_command)
|
||||||
|
|
||||||
|
@patch('magnum.conductor.handlers.common.kube_utils._k8s_create_with_path')
|
||||||
|
@patch('tempfile.NamedTemporaryFile')
|
||||||
|
def test_k8s_create_with_data(self,
|
||||||
|
mock_named_tempfile,
|
||||||
|
mock_k8s_create):
|
||||||
|
expected_master_address = 'master_address'
|
||||||
|
expected_data = 'resource_data'
|
||||||
|
expected_filename = 'resource_file'
|
||||||
|
|
||||||
|
mock_file = mock.MagicMock()
|
||||||
|
mock_file.name = expected_filename
|
||||||
|
mock_named_tempfile.return_value.__enter__.return_value = mock_file
|
||||||
|
|
||||||
|
kube_utils._k8s_create_with_data(expected_master_address,
|
||||||
|
expected_data)
|
||||||
|
|
||||||
|
mock_file.write.assert_called_once_with(expected_data)
|
||||||
|
mock_k8s_create.assert_called_once_with(expected_master_address,
|
||||||
|
expected_filename)
|
||||||
|
|
||||||
|
@patch('magnum.conductor.handlers.common.kube_utils._k8s_update_with_data')
|
||||||
|
@patch('magnum.conductor.handlers.common.kube_utils.'
|
||||||
|
'_extract_resource_data')
|
||||||
|
@patch('magnum.conductor.handlers.common.kube_utils.'
|
||||||
|
'_extract_resource_definition_url')
|
||||||
|
def test_k8s_update_data(self,
|
||||||
|
mock_definition_url,
|
||||||
|
mock_data,
|
||||||
|
mock_update_with_data):
|
||||||
|
expected_data = 'data'
|
||||||
|
master_address = 'master_address'
|
||||||
|
mock_data.return_value = expected_data
|
||||||
|
mock_definition_url.return_value = None
|
||||||
|
mock_resource = mock.MagicMock()
|
||||||
|
|
||||||
|
kube_utils._k8s_update(master_address, mock_resource)
|
||||||
|
mock_update_with_data.assert_called_once_with(master_address,
|
||||||
|
expected_data)
|
||||||
|
|
||||||
|
@patch('magnum.conductor.handlers.common.kube_utils._k8s_update_with_path')
|
||||||
|
@patch('magnum.conductor.handlers.common.kube_utils.'
|
||||||
|
'_extract_resource_data')
|
||||||
|
@patch('magnum.conductor.handlers.common.kube_utils.'
|
||||||
|
'_extract_resource_definition_url')
|
||||||
|
def test_k8s_update_url(self,
|
||||||
|
mock_definition_url,
|
||||||
|
mock_data,
|
||||||
|
mock_update_with_path):
|
||||||
|
expected_url = 'url'
|
||||||
|
master_address = 'master_address'
|
||||||
|
mock_data.return_value = None
|
||||||
|
mock_definition_url.return_value = expected_url
|
||||||
|
mock_resource = mock.MagicMock()
|
||||||
|
|
||||||
|
kube_utils._k8s_update(master_address, mock_resource)
|
||||||
|
mock_update_with_path.assert_called_once_with(master_address,
|
||||||
|
expected_url)
|
||||||
|
|
||||||
|
@patch('magnum.openstack.common.utils.trycmd')
|
||||||
|
def test_k8s_update_with_path(self, mock_trycmd):
|
||||||
|
expected_master_address = 'master_address'
|
||||||
|
expected_pod_file = 'pod_file'
|
||||||
|
expected_command = [
|
||||||
|
'kubectl', 'update',
|
||||||
|
'-s', expected_master_address,
|
||||||
|
'-f', expected_pod_file
|
||||||
|
]
|
||||||
|
|
||||||
|
kube_utils._k8s_update_with_path(expected_master_address,
|
||||||
|
expected_pod_file)
|
||||||
|
mock_trycmd.assert_called_once_with(*expected_command)
|
||||||
|
|
||||||
|
@patch('magnum.conductor.handlers.common.kube_utils._k8s_update_with_path')
|
||||||
|
@patch('tempfile.NamedTemporaryFile')
|
||||||
|
def test_k8s_update_with_data(self,
|
||||||
|
mock_named_tempfile,
|
||||||
|
mock_k8s_update):
|
||||||
|
expected_master_address = 'master_address'
|
||||||
|
expected_data = 'resource_data'
|
||||||
|
expected_filename = 'resource_file'
|
||||||
|
|
||||||
|
mock_file = mock.MagicMock()
|
||||||
|
mock_file.name = expected_filename
|
||||||
|
mock_named_tempfile.return_value.__enter__.return_value = mock_file
|
||||||
|
|
||||||
|
kube_utils._k8s_update_with_data(expected_master_address,
|
||||||
|
expected_data)
|
||||||
|
|
||||||
|
mock_file.write.assert_called_once_with(expected_data)
|
||||||
|
mock_k8s_update.assert_called_once_with(expected_master_address,
|
||||||
|
expected_filename)
|
Loading…
Reference in New Issue