Implement 'personality' plugin for V2.1

User can specify 'personality' attribute while server creation
& rebuild to inject files into server.

This patch implement this as separate plugin for V2.1 API.

Also define the schema and unit tests for the same.

Partially implements blueprint v2-on-v3-api

Change-Id: Ia0c527539af7fe33eba4999822476653e1b96bc6
This commit is contained in:
ghanshyam 2014-10-30 13:34:11 +09:00
parent b40ea7c0ec
commit d99b308e6d
14 changed files with 421 additions and 2 deletions

View File

@ -0,0 +1,15 @@
{
"rebuild": {
"imageRef": "http://glance.openstack.example.com/images/70a599e0-31e7-49b7-b260-868f441e862b",
"name": "new-server-test",
"metadata": {
"meta_var": "meta_val"
},
"personality": [
{
"path": "/etc/banner.txt",
"contents": "ICAgICAgDQoiQSBjbG91ZCBkb2VzIG5vdCBrbm93IHdoeSBp dCBtb3ZlcyBpbiBqdXN0IHN1Y2ggYSBkaXJlY3Rpb24gYW5k IGF0IHN1Y2ggYSBzcGVlZC4uLkl0IGZlZWxzIGFuIGltcHVs c2lvbi4uLnRoaXMgaXMgdGhlIHBsYWNlIHRvIGdvIG5vdy4g QnV0IHRoZSBza3kga25vd3MgdGhlIHJlYXNvbnMgYW5kIHRo ZSBwYXR0ZXJucyBiZWhpbmQgYWxsIGNsb3VkcywgYW5kIHlv dSB3aWxsIGtub3csIHRvbywgd2hlbiB5b3UgbGlmdCB5b3Vy c2VsZiBoaWdoIGVub3VnaCB0byBzZWUgYmV5b25kIGhvcml6 b25zLiINCg0KLVJpY2hhcmQgQmFjaA=="
}
]
}
}

View File

@ -0,0 +1,55 @@
{
"server": {
"addresses": {
"private": [
{
"addr": "192.168.0.3",
"OS-EXT-IPS-MAC:mac_addr": "aa:bb:cc:dd:ee:ff",
"OS-EXT-IPS:type": "fixed",
"version": 4
}
]
},
"adminPass": "99WHAxN8gpvg",
"created": "2013-11-06T07:51:09Z",
"flavor": {
"id": "1",
"links": [
{
"href": "http://openstack.example.com/flavors/1",
"rel": "bookmark"
}
]
},
"hostId": "5c8072dbcda8ce3f26deb6662bd7718e1a6d349bdf2296911d1be4ac",
"id": "53a63a19-c145-47f8-9ae5-b39d6bff33ec",
"image": {
"id": "70a599e0-31e7-49b7-b260-868f441e862b",
"links": [
{
"href": "http://openstack.example.com/images/70a599e0-31e7-49b7-b260-868f441e862b",
"rel": "bookmark"
}
]
},
"links": [
{
"href": "http://openstack.example.com/v3/servers/53a63a19-c145-47f8-9ae5-b39d6bff33ec",
"rel": "self"
},
{
"href": "http://openstack.example.com/servers/53a63a19-c145-47f8-9ae5-b39d6bff33ec",
"rel": "bookmark"
}
],
"metadata": {
"meta_var": "meta_val"
},
"name": "new-server-test",
"progress": 0,
"status": "ACTIVE",
"tenant_id": "openstack",
"updated": "2013-11-06T07:51:11Z",
"user_id": "fake"
}
}

View File

@ -0,0 +1,16 @@
{
"server": {
"name": "new-server-test",
"imageRef": "http://openstack.example.com/openstack/images/70a599e0-31e7-49b7-b260-868f441e862b",
"flavorRef": "http://openstack.example.com/openstack/flavors/1",
"metadata": {
"My Server Name": "Apache1"
},
"personality": [
{
"path": "/etc/banner.txt",
"contents": "ICAgICAgDQoiQSBjbG91ZCBkb2VzIG5vdCBrbm93IHdoeSBpdCBtb3ZlcyBpbiBqdXN0IHN1Y2ggYSBkaXJlY3Rpb24gYW5kIGF0IHN1Y2ggYSBzcGVlZC4uLkl0IGZlZWxzIGFuIGltcHVsc2lvbi4uLnRoaXMgaXMgdGhlIHBsYWNlIHRvIGdvIG5vdy4gQnV0IHRoZSBza3kga25vd3MgdGhlIHJlYXNvbnMgYW5kIHRoZSBwYXR0ZXJucyBiZWhpbmQgYWxsIGNsb3VkcywgYW5kIHlvdSB3aWxsIGtub3csIHRvbywgd2hlbiB5b3UgbGlmdCB5b3Vyc2VsZiBoaWdoIGVub3VnaCB0byBzZWUgYmV5b25kIGhvcml6b25zLiINCg0KLVJpY2hhcmQgQmFjaA=="
}
]
}
}

View File

@ -0,0 +1,16 @@
{
"server": {
"adminPass": "n7JGBda664QG",
"id": "934760e1-2b0b-4f9e-a916-eac1e69839dc",
"links": [
{
"href": "http://openstack.example.com/v3/servers/934760e1-2b0b-4f9e-a916-eac1e69839dc",
"rel": "self"
},
{
"href": "http://openstack.example.com/servers/934760e1-2b0b-4f9e-a916-eac1e69839dc",
"rel": "bookmark"
}
]
}
}

View File

@ -230,6 +230,7 @@
"compute_extension:v3:os-pci:index": "rule:admin_api",
"compute_extension:v3:os-pci:detail": "rule:admin_api",
"compute_extension:v3:os-pci:show": "rule:admin_api",
"compute_extension:v3:os-personality:discoverable": "",
"compute_extension:quotas:show": "",
"compute_extension:quotas:update": "rule:admin_api",
"compute_extension:quotas:delete": "rule:admin_api",

View File

@ -0,0 +1,65 @@
# 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 nova.api.openstack.compute.schemas.v3 import personality
from nova.api.openstack import extensions
ALIAS = "os-personality"
class Personality(extensions.V3APIExtensionBase):
"""Personality support."""
name = "Personality"
alias = ALIAS
version = 1
def get_controller_extensions(self):
return []
def get_resources(self):
return []
def _get_injected_files(self, personality):
"""Create a list of injected files from the personality attribute.
At this time, injected_files must be formatted as a list of
(file_path, file_content) pairs for compatibility with the
underlying compute service.
"""
injected_files = []
for item in personality:
injected_files.append((item['path'], item['contents']))
return injected_files
# NOTE(gmann): This function is not supposed to use 'body_deprecated_param'
# parameter as this is placed to handle scheduler_hint extension for V2.1.
# making 'body_deprecated_param' as optional to avoid changes for
# server_update & server_rebuild
def server_create(self, server_dict, create_kwargs,
body_deprecated_param=None):
if 'personality' in server_dict:
create_kwargs['injected_files'] = self._get_injected_files(
server_dict['personality'])
def server_rebuild(self, server_dict, create_kwargs,
body_deprecated_param=None):
if 'personality' in server_dict:
create_kwargs['files_to_inject'] = self._get_injected_files(
server_dict['personality'])
def get_server_create_schema(self):
return personality.server_create
get_server_rebuild_schema = get_server_create_schema

View File

@ -0,0 +1,30 @@
# 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.
server_create = {
'personality': {
'type': 'array',
'items': {
'type': 'object',
'properties': {
'path': {'type': 'string'},
'contents': {
'type': 'string',
'format': 'base64'
}
},
'additionalProperties': False,
}
}
}

View File

@ -0,0 +1,15 @@
{
"rebuild": {
"imageRef": "%(glance_host)s/images/%(image_id)s",
"name": "new-server-test",
"metadata": {
"meta_var": "meta_val"
},
"personality": [
{
"path": "/etc/banner.txt",
"contents": "ICAgICAgDQoiQSBjbG91ZCBkb2VzIG5vdCBrbm93IHdoeSBp dCBtb3ZlcyBpbiBqdXN0IHN1Y2ggYSBkaXJlY3Rpb24gYW5k IGF0IHN1Y2ggYSBzcGVlZC4uLkl0IGZlZWxzIGFuIGltcHVs c2lvbi4uLnRoaXMgaXMgdGhlIHBsYWNlIHRvIGdvIG5vdy4g QnV0IHRoZSBza3kga25vd3MgdGhlIHJlYXNvbnMgYW5kIHRo ZSBwYXR0ZXJucyBiZWhpbmQgYWxsIGNsb3VkcywgYW5kIHlv dSB3aWxsIGtub3csIHRvbywgd2hlbiB5b3UgbGlmdCB5b3Vy c2VsZiBoaWdoIGVub3VnaCB0byBzZWUgYmV5b25kIGhvcml6 b25zLiINCg0KLVJpY2hhcmQgQmFjaA=="
}
]
}
}

View File

@ -0,0 +1,55 @@
{
"server": {
"addresses": {
"private": [
{
"addr": "%(ip)s",
"version": 4,
"OS-EXT-IPS-MAC:mac_addr": "aa:bb:cc:dd:ee:ff",
"OS-EXT-IPS:type": "fixed"
}
]
},
"adminPass": "%(password)s",
"created": "%(isotime)s",
"flavor": {
"id": "1",
"links": [
{
"href": "%(host)s/flavors/1",
"rel": "bookmark"
}
]
},
"hostId": "%(hostid)s",
"id": "%(uuid)s",
"image": {
"id": "%(image_id)s",
"links": [
{
"href": "%(host)s/images/%(image_id)s",
"rel": "bookmark"
}
]
},
"links": [
{
"href": "%(host)s/v3/servers/%(uuid)s",
"rel": "self"
},
{
"href": "%(host)s/servers/%(uuid)s",
"rel": "bookmark"
}
],
"metadata": {
"meta_var": "meta_val"
},
"name": "new-server-test",
"progress": 0,
"status": "ACTIVE",
"tenant_id": "openstack",
"updated": "%(isotime)s",
"user_id": "fake"
}
}

View File

@ -0,0 +1,16 @@
{
"server": {
"name": "new-server-test",
"imageRef": "%(host)s/openstack/images/%(image_id)s",
"flavorRef": "%(host)s/openstack/flavors/1",
"metadata": {
"My Server Name": "Apache1"
},
"personality": [
{
"path": "/etc/banner.txt",
"contents": "ICAgICAgDQoiQSBjbG91ZCBkb2VzIG5vdCBrbm93IHdoeSBpdCBtb3ZlcyBpbiBqdXN0IHN1Y2ggYSBkaXJlY3Rpb24gYW5kIGF0IHN1Y2ggYSBzcGVlZC4uLkl0IGZlZWxzIGFuIGltcHVsc2lvbi4uLnRoaXMgaXMgdGhlIHBsYWNlIHRvIGdvIG5vdy4gQnV0IHRoZSBza3kga25vd3MgdGhlIHJlYXNvbnMgYW5kIHRoZSBwYXR0ZXJucyBiZWhpbmQgYWxsIGNsb3VkcywgYW5kIHlvdSB3aWxsIGtub3csIHRvbywgd2hlbiB5b3UgbGlmdCB5b3Vyc2VsZiBoaWdoIGVub3VnaCB0byBzZWUgYmV5b25kIGhvcml6b25zLiINCg0KLVJpY2hhcmQgQmFjaA=="
}
]
}
}

View File

@ -0,0 +1,16 @@
{
"server": {
"adminPass": "%(password)s",
"id": "%(id)s",
"links": [
{
"href": "http://openstack.example.com/v3/servers/%(uuid)s",
"rel": "self"
},
{
"href": "http://openstack.example.com/servers/%(uuid)s",
"rel": "bookmark"
}
]
}
}

View File

@ -0,0 +1,45 @@
# 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 nova.tests.functional.v3 import api_sample_base
from nova.tests.unit.image import fake
class PersonalitySampleJsonTest(api_sample_base.ApiSampleTestBaseV3):
extension_name = 'os-personality'
def _servers_post(self, subs):
response = self._do_post('servers', 'server-post-req', subs)
subs.update(self._get_regexes())
return self._verify_response('server-post-resp', subs, response, 202)
def test_servers_post(self):
subs = {
'image_id': fake.get_valid_image_id(),
'host': self._get_host()
}
self._servers_post(subs)
def test_servers_rebuild(self):
subs = {
'image_id': fake.get_valid_image_id(),
'host': self._get_host()
}
uuid = self._servers_post(subs)
response = self._do_post('servers/%s/action' % uuid,
'server-action-rebuild-req', subs)
subs['hostid'] = '[a-f0-9]+'
subs['id'] = uuid
self._verify_response('server-action-rebuild-resp',
subs, response, 202)

View File

@ -1530,6 +1530,37 @@ class ServersControllerRebuildInstanceTest(ControllerTest):
self.controller._action_rebuild,
self.req, FAKE_UUID, body=self.body)
def test_rebuild_bad_personality(self):
body = {
"rebuild": {
"imageRef": self.image_href,
"personality": [{
"path": "/path/to/file",
"contents": "INVALID b64",
}]
},
}
self.assertRaises(exception.ValidationError,
self.controller._action_rebuild,
self.req, FAKE_UUID, body=body)
def test_rebuild_personality(self):
body = {
"rebuild": {
"imageRef": self.image_href,
"personality": [{
"path": "/path/to/file",
"contents": base64.b64encode("Test String"),
}]
},
}
body = self.controller._action_rebuild(self.req, FAKE_UUID,
body=body).obj
self.assertNotIn('personality', body['server'])
def test_start(self):
self.mox.StubOutWithMock(compute_api.API, 'start')
compute_api.API.start(mox.IgnoreArg(), mox.IgnoreArg())
@ -1946,8 +1977,14 @@ class ServersControllerCreateTest(test.TestCase):
'hello': 'world',
'open': 'stack',
},
},
}
'personality': [
{
"path": "/etc/banner.txt",
"contents": "MQ==",
},
],
},
}
self.bdm = [{'delete_on_termination': 1,
'device_name': 123,
'volume_size': 1,
@ -2646,6 +2683,40 @@ class ServersControllerCreateTest(test.TestCase):
self.controller.create,
self.req, body=self.body)
@mock.patch.object(compute_api.API, 'create')
def test_create_instance_invalid_personality(self, mock_create):
codec = 'utf8'
content = 'b25zLiINCg0KLVJpY2hhcmQgQ$$%QQmFjaA=='
start_position = 19
end_position = 20
msg = 'invalid start byte'
mock_create.side_effect = UnicodeDecodeError(codec, content,
start_position,
end_position, msg)
self.body['server']['personality'] = [
{
"path": "/etc/banner.txt",
"contents": "b25zLiINCg0KLVJpY2hhcmQgQ$$%QQmFjaA==",
},
]
self.req.body = jsonutils.dumps(self.body)
self.assertRaises(webob.exc.HTTPBadRequest,
self.controller.create, self.req, body=self.body)
def test_create_instance_with_extra_personality_arg(self):
self.body['server']['personality'] = [
{
"path": "/etc/banner.txt",
"contents": "b25zLiINCg0KLVJpY2hhcmQgQ$$%QQmFjaA==",
"extra_arg": "extra value"
},
]
self.assertRaises(exception.ValidationError,
self.controller.create,
self.req, body=self.body)
class ServersControllerCreateTestWithMock(test.TestCase):
image_uuid = '76fa36fc-c930-4bf3-8c8a-ea2a2420deb6'

View File

@ -111,6 +111,7 @@ nova.api.v3.extensions =
networks_associate = nova.api.openstack.compute.plugins.v3.networks_associate:NetworksAssociate
pause_server = nova.api.openstack.compute.plugins.v3.pause_server:PauseServer
pci = nova.api.openstack.compute.plugins.v3.pci:Pci
personality = nova.api.openstack.compute.plugins.v3.personality:Personality
quota_classes = nova.api.openstack.compute.plugins.v3.quota_classes:QuotaClasses
quota_sets = nova.api.openstack.compute.plugins.v3.quota_sets:QuotaSets
remote_consoles = nova.api.openstack.compute.plugins.v3.remote_consoles:RemoteConsoles
@ -143,6 +144,7 @@ nova.api.v3.extensions.server.create =
disk_config = nova.api.openstack.compute.plugins.v3.disk_config:DiskConfig
keypairs_create = nova.api.openstack.compute.plugins.v3.keypairs:Keypairs
multiple_create = nova.api.openstack.compute.plugins.v3.multiple_create:MultipleCreate
personality = nova.api.openstack.compute.plugins.v3.personality:Personality
scheduler_hints = nova.api.openstack.compute.plugins.v3.scheduler_hints:SchedulerHints
security_groups = nova.api.openstack.compute.plugins.v3.security_groups:SecurityGroups
user_data = nova.api.openstack.compute.plugins.v3.user_data:UserData
@ -150,6 +152,7 @@ nova.api.v3.extensions.server.create =
nova.api.v3.extensions.server.rebuild =
access_ips = nova.api.openstack.compute.plugins.v3.access_ips:AccessIPs
disk_config = nova.api.openstack.compute.plugins.v3.disk_config:DiskConfig
personality = nova.api.openstack.compute.plugins.v3.personality:Personality
nova.api.v3.extensions.server.update =
access_ips = nova.api.openstack.compute.plugins.v3.access_ips:AccessIPs