Add a glance module that allows uploading images

Add a new module, glance, that allows uploading or finding kernel
and ramdisk images that are in Glance. This allows us to put
setup-baremetal in -incubator on a radical diet.

Also adds a command-line utility, upload_kernel_ramdisk to make
adoption in -incubator easier.

Change-Id: I2708646090cf9435f8910ee2170a5ff2939ec27a
This commit is contained in:
Steve Kowalik 2014-12-04 14:12:58 +11:00
parent e3abc1c269
commit fc91c84a2a
5 changed files with 230 additions and 0 deletions

View File

@ -0,0 +1,38 @@
# Copyright (c) 2014 Hewlett-Packard Development Company, L.P.
#
# 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.
import sys
import mock
from os_cloud_config.cmd import upload_kernel_ramdisk
from os_cloud_config.tests import base
class UploadKernelRamdiskTest(base.TestCase):
@mock.patch('os_cloud_config.cmd.utils._clients.get_glance_client',
return_value='glance_client_mock')
@mock.patch('os_cloud_config.glance.create_or_find_kernel_and_ramdisk')
@mock.patch.dict('os.environ', {'OS_USERNAME': 'a', 'OS_PASSWORD': 'a',
'OS_TENANT_NAME': 'a', 'OS_AUTH_URL': 'a'})
@mock.patch.object(sys, 'argv', ['upload_kernel_ramdisk', '-k',
'bm-kernel', '-r', 'bm-ramdisk', '-l',
'kernel-file', '-s', 'ramdisk-file'])
def test_with_arguments(self, create_or_find_mock, glanceclient_mock):
upload_kernel_ramdisk.main()
create_or_find_mock.assert_called_once_with(
'glance_client_mock', 'bm-kernel', 'bm-ramdisk',
kernel_path='kernel-file', ramdisk_path='ramdisk-file')

View File

@ -0,0 +1,56 @@
# 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.
import argparse
import logging
import textwrap
from os_cloud_config.cmd.utils import _clients as clients
from os_cloud_config.cmd.utils import environment
from os_cloud_config import glance
def parse_args():
description = textwrap.dedent("""
Uploads the provided kernel and ramdisk to a Glance store.
""")
parser = argparse.ArgumentParser(
description=description,
formatter_class=argparse.RawDescriptionHelpFormatter,
)
parser.add_argument('-k', '--kernel', dest='kernel',
help='Name of the kernel image', required=True)
parser.add_argument('-l' '--kernel-file', dest='kernel_file',
help='Kernel to upload', required=True)
parser.add_argument('-r', '--ramdisk', dest='ramdisk',
help='Name of the ramdisk image', required=True)
parser.add_argument('-s', '--ramdisk-file', dest='ramdisk_file',
help='Ramdisk to upload', required=True)
environment._add_logging_arguments(parser)
return parser.parse_args()
def main():
args = parse_args()
environment._configure_logging(args)
try:
environment._ensure()
client = clients.get_glance_client()
glance.create_or_find_kernel_and_ramdisk(
client, args.kernel, args.ramdisk, kernel_path=args.kernel_file,
ramdisk_path=args.ramdisk_file)
except Exception:
logging.exception("Unexpected error during command execution")
return 1
return 0

61
os_cloud_config/glance.py Normal file
View File

@ -0,0 +1,61 @@
# Copyright (c) 2014 Hewlett-Packard Development Company, L.P.
#
# 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.
import logging
from glanceclient.openstack.common.apiclient import exceptions
LOG = logging.getLogger(__name__)
def create_or_find_kernel_and_ramdisk(glanceclient, kernel_name, ramdisk_name,
kernel_path=None, ramdisk_path=None):
"""Find or create a given kernel and ramdisk in Glance.
If either kernel_path or ramdisk_path is None, they will not be created,
and an exception will be raised if it does not exist in Glance.
:param glanceclient: A client for Glance.
:param kernel_name: Name to search for or create for the kernel.
:param ramdisk_name: Name to search for or create for the ramdisk.
:param kernel_path: Path to the kernel on disk.
:param ramdisk_path: Path to the ramdisk on disk.
:returns: A dictionary mapping kernel or ramdisk to the ID in Glance.
"""
try:
kernel_image = glanceclient.images.find(name=kernel_name,
disk_format='aki')
except exceptions.NotFound:
if kernel_path:
kernel_image = glanceclient.images.create(
name=kernel_name, disk_format='aki', is_public=True,
data=open(kernel_path, 'rb'))
else:
raise ValueError("Kernel image not found in Glance, and no path "
"specified.")
try:
ramdisk_image = glanceclient.images.find(name=ramdisk_name,
disk_format='ari')
except exceptions.NotFound:
if ramdisk_path:
# public, type=ari
ramdisk_image = glanceclient.images.create(
name=ramdisk_name, disk_format='ari', is_public=True,
data=open(ramdisk_path, 'rb'))
else:
raise ValueError("Ramdisk image not found in Glance, and no path "
"specified.")
return {'kernel': kernel_image.id, 'ramdisk': ramdisk_image.id}

View File

@ -0,0 +1,74 @@
# Copyright (c) 2014 Hewlett-Packard Development Company, L.P.
#
# 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.
import collections
import tempfile
from glanceclient.openstack.common.apiclient import exceptions
import mock
import testtools
from os_cloud_config import glance
from os_cloud_config.tests import base
class GlanceTest(base.TestCase):
def setUp(self):
super(GlanceTest, self).setUp()
self.image = collections.namedtuple('image', ['id'])
def test_return_existing_kernel_and_ramdisk(self):
client = mock.MagicMock()
expected = {'kernel': 'aaa', 'ramdisk': 'zzz'}
client.images.find.side_effect = (self.image('aaa'), self.image('zzz'))
ids = glance.create_or_find_kernel_and_ramdisk(client, 'bm-kernel',
'bm-ramdisk')
client.images.create.assert_not_called()
self.assertEqual(expected, ids)
def test_raise_exception_kernel(self):
client = mock.MagicMock()
client.images.find.side_effect = exceptions.NotFound
message = "Kernel image not found in Glance, and no path specified."
with testtools.ExpectedException(ValueError, message):
glance.create_or_find_kernel_and_ramdisk(client, 'bm-kernel',
None)
def test_raise_exception_ramdisk(self):
client = mock.MagicMock()
client.images.find.side_effect = (self.image('aaa'),
exceptions.NotFound)
message = "Ramdisk image not found in Glance, and no path specified."
with testtools.ExpectedException(ValueError, message):
glance.create_or_find_kernel_and_ramdisk(client, 'bm-kernel',
'bm-ramdisk')
def test_create_kernel_and_ramdisk(self):
client = mock.MagicMock()
client.images.find.side_effect = exceptions.NotFound
client.images.create.side_effect = (self.image('aaa'),
self.image('zzz'))
expected = {'kernel': 'aaa', 'ramdisk': 'zzz'}
with tempfile.NamedTemporaryFile() as imagefile:
ids = glance.create_or_find_kernel_and_ramdisk(
client, 'bm-kernel', 'bm-ramdisk', kernel_path=imagefile.name,
ramdisk_path=imagefile.name)
kernel_create = mock.call(name='bm-kernel', disk_format='aki',
is_public=True, data=mock.ANY)
ramdisk_create = mock.call(name='bm-ramdisk', disk_format='ari',
is_public=True, data=mock.ANY)
client.images.create.assert_has_calls([kernel_create, ramdisk_create])
self.assertEqual(expected, ids)

View File

@ -31,6 +31,7 @@ console_scripts =
setup-endpoints = os_cloud_config.cmd.setup_endpoints:main
setup-flavors = os_cloud_config.cmd.setup_flavors:main
setup-neutron = os_cloud_config.cmd.setup_neutron:main
upload-kernel-ramdisk = os_cloud_config.cmd.upload_kernel_ramdisk:main
[build_sphinx]
source-dir = doc/source