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:
OTSUKA, Yuanying 2015-01-06 17:06:29 +09:00
parent be136bbd8e
commit 9ff6b93e08
3 changed files with 287 additions and 65 deletions

View File

@ -12,12 +12,74 @@
"""Magnum Kubernetes RPC handler."""
import tempfile
from magnum.openstack.common import log as logging
from magnum.openstack.common import utils
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):
"""These are the backend operations. They are executed by the backend
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):
LOG.debug("service_create with contents %s" % service)
try:
if service.service_definition_url:
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', '-')
out, err = _k8s_create(master_address, service)
if err:
return False
except Exception as e:
@ -52,15 +108,8 @@ class KubeClient(object):
def service_update(self, master_address, service):
LOG.debug("service_update with contents %s" % service)
try:
if service.service_definition_url:
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', '-')
out, err = _k8s_update(master_address, service)
if err:
return False
except Exception as e:
@ -117,43 +166,29 @@ class KubeClient(object):
return None
# Pod Operations
def pod_create(self, master_address, contents):
LOG.debug("pod_create contents %s" % contents)
def pod_create(self, master_address, pod):
LOG.debug("pod_create contents %s" % pod)
try:
if contents.pod_definition_url:
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', '-')
out, err = _k8s_create(master_address, pod)
if err:
return False
except Exception as e:
LOG.error("Couldn't create pod with contents %s due to error %s"
% (contents, e))
% (pod, e))
return False
return True
def pod_update(self, master_address, contents):
LOG.debug("pod_update contents %s" % contents)
def pod_update(self, master_address, pod):
LOG.debug("pod_update contents %s" % pod)
try:
if contents.pod_definition_url:
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', '-')
out, err = _k8s_update(master_address, pod)
if err:
return False
except Exception as e:
LOG.error("Couldn't update pod with contents %s due to error %s"
% (contents, e))
% (pod, e))
return False
return True
@ -202,43 +237,29 @@ class KubeClient(object):
return None
# Replication Controller Operations
def rc_create(self, master_address, contents):
LOG.debug("rc_create contents %s" % contents)
def rc_create(self, master_address, rc):
LOG.debug("rc_create contents %s" % rc)
try:
if contents.rc_definition_url:
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', '-')
out, err = _k8s_create(master_address, rc)
if err:
return False
except Exception as e:
LOG.error("Couldn't create rc with contents %s due to error %s"
% (contents, e))
% (rc, e))
return False
return True
def rc_update(self, master_address, contents):
LOG.debug("rc_update contents %s" % contents)
def rc_update(self, master_address, rc):
LOG.debug("rc_update contents %s" % rc)
try:
if contents.rc_definition_url:
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', '-')
out, err = _k8s_update(master_address, rc)
if err:
return False
except Exception as e:
LOG.error("Couldn't update rc with contents %s due to error %s"
% (contents, e))
% (rc, e))
return False
return True

View File

@ -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)