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.""" """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

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)