Merge "Support string flavor IDs"
This commit is contained in:
commit
c7cb6ef711
@ -1,5 +1,5 @@
|
||||
HTTP/1.1 200 OK
|
||||
Content-Type: application/json
|
||||
Content-Length: 199
|
||||
Content-Length: 214
|
||||
Date: Mon, 18 Mar 2013 19:09:17 GMT
|
||||
|
||||
|
@ -12,7 +12,7 @@
|
||||
}
|
||||
],
|
||||
"name": "m1.tiny",
|
||||
"ram": 512
|
||||
"ram": 512,
|
||||
"str_id": "1"
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
HTTP/1.1 200 OK
|
||||
Content-Type: application/json
|
||||
Content-Length: 2320
|
||||
Content-Length: 2730
|
||||
Date: Mon, 18 Mar 2013 19:09:17 GMT
|
||||
|
||||
|
@ -13,7 +13,8 @@
|
||||
}
|
||||
],
|
||||
"name": "m1.tiny",
|
||||
"ram": 512
|
||||
"ram": 512,
|
||||
"str_id": "1"
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
@ -28,7 +29,8 @@
|
||||
}
|
||||
],
|
||||
"name": "m1.small",
|
||||
"ram": 2048
|
||||
"ram": 2048,
|
||||
"str_id": "2"
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
@ -43,7 +45,8 @@
|
||||
}
|
||||
],
|
||||
"name": "m1.medium",
|
||||
"ram": 4096
|
||||
"ram": 4096,
|
||||
"str_id": "3"
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
@ -58,7 +61,8 @@
|
||||
}
|
||||
],
|
||||
"name": "m1.large",
|
||||
"ram": 8192
|
||||
"ram": 8192,
|
||||
"str_id": "4"
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
@ -73,7 +77,8 @@
|
||||
}
|
||||
],
|
||||
"name": "m1.xlarge",
|
||||
"ram": 16384
|
||||
"ram": 16384,
|
||||
"str_id": "5"
|
||||
},
|
||||
{
|
||||
"id": 6,
|
||||
@ -88,7 +93,8 @@
|
||||
}
|
||||
],
|
||||
"name": "m1.nano",
|
||||
"ram": 64
|
||||
"ram": 64,
|
||||
"str_id": "6"
|
||||
},
|
||||
{
|
||||
"id": 7,
|
||||
@ -103,7 +109,8 @@
|
||||
}
|
||||
],
|
||||
"name": "m1.micro",
|
||||
"ram": 128
|
||||
"ram": 128,
|
||||
"str_id": "7"
|
||||
},
|
||||
{
|
||||
"id": 8,
|
||||
@ -118,7 +125,8 @@
|
||||
}
|
||||
],
|
||||
"name": "m1.rd-smaller",
|
||||
"ram": 768
|
||||
"ram": 768,
|
||||
"str_id": "8"
|
||||
},
|
||||
{
|
||||
"id": 9,
|
||||
@ -133,7 +141,8 @@
|
||||
}
|
||||
],
|
||||
"name": "tinier",
|
||||
"ram": 506
|
||||
"ram": 506,
|
||||
"str_id": "9"
|
||||
},
|
||||
{
|
||||
"id": 10,
|
||||
@ -148,7 +157,8 @@
|
||||
}
|
||||
],
|
||||
"name": "m1.rd-tiny",
|
||||
"ram": 512
|
||||
"ram": 512,
|
||||
"str_id": "10"
|
||||
},
|
||||
{
|
||||
"id": 11,
|
||||
@ -163,7 +173,8 @@
|
||||
}
|
||||
],
|
||||
"name": "eph.rd-tiny",
|
||||
"ram": 512
|
||||
"ram": 512,
|
||||
"str_id": "11"
|
||||
},
|
||||
{
|
||||
"id": 12,
|
||||
@ -178,8 +189,24 @@
|
||||
}
|
||||
],
|
||||
"name": "eph.rd-smaller",
|
||||
"ram": 768
|
||||
"ram": 768,
|
||||
"str_id": "12"
|
||||
},
|
||||
{
|
||||
"id": null,
|
||||
"links": [
|
||||
{
|
||||
"href": "https://troveapi.org/v1.0/1234/flavors/custom",
|
||||
"rel": "self"
|
||||
},
|
||||
{
|
||||
"href": "https://troveapi.org/flavors/custom",
|
||||
"rel": "bookmark"
|
||||
}
|
||||
],
|
||||
"name": "custom.small",
|
||||
"ram": 512,
|
||||
"str_id": "custom"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
|
@ -10,9 +10,8 @@
|
||||
}
|
||||
],
|
||||
"name": "hostname_1",
|
||||
"percentUsed": 204,
|
||||
"totalRAM": 2004,
|
||||
"percentUsed": 12,
|
||||
"totalRAM": 32000,
|
||||
"usedRAM": 4096
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -114,6 +114,12 @@
|
||||
"name": "eph.rd-smaller",
|
||||
"ram": 768,
|
||||
"local_storage": 2
|
||||
},
|
||||
{
|
||||
"id": "custom",
|
||||
"name": "custom.small",
|
||||
"ram": 512,
|
||||
"local_storage": 1
|
||||
}
|
||||
|
||||
],
|
||||
|
@ -30,6 +30,13 @@ boolean_string = {
|
||||
"maximum": 1
|
||||
}
|
||||
|
||||
non_empty_string = {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 255,
|
||||
"pattern": "^.*[0-9a-zA-Z]+.*$"
|
||||
}
|
||||
|
||||
configuration_data_types = {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
@ -51,12 +58,7 @@ configuration_non_empty_string = {
|
||||
|
||||
flavorref = {
|
||||
'oneOf': [
|
||||
url_ref,
|
||||
{
|
||||
"type": "string",
|
||||
"maxLength": 5,
|
||||
"pattern": "[0-9]+"
|
||||
},
|
||||
non_empty_string,
|
||||
{
|
||||
"type": "integer"
|
||||
}]
|
||||
@ -75,13 +77,6 @@ volume_size = {
|
||||
}]
|
||||
}
|
||||
|
||||
non_empty_string = {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 255,
|
||||
"pattern": "^.*[0-9a-zA-Z]+.*$"
|
||||
}
|
||||
|
||||
host_string = {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
|
@ -0,0 +1,35 @@
|
||||
#
|
||||
# Copyright 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.
|
||||
|
||||
from sqlalchemy.schema import MetaData
|
||||
|
||||
from trove.db.sqlalchemy.migrate_repo.schema import Integer
|
||||
from trove.db.sqlalchemy.migrate_repo.schema import String
|
||||
from trove.db.sqlalchemy.migrate_repo.schema import Table
|
||||
|
||||
|
||||
meta = MetaData()
|
||||
|
||||
|
||||
def upgrade(migrate_engine):
|
||||
meta.bind = migrate_engine
|
||||
instances = Table('instances', meta, autoload=True)
|
||||
instances.c.flavor_id.alter(String(255))
|
||||
|
||||
|
||||
def downgrade(migrate_engine):
|
||||
meta.bind = migrate_engine
|
||||
instances = Table('instances', meta, autoload=True)
|
||||
instances.c.flavor_id.alter(Integer())
|
@ -14,6 +14,8 @@
|
||||
# under the License.
|
||||
|
||||
|
||||
import six
|
||||
|
||||
from trove.common import exception
|
||||
from trove.common import wsgi
|
||||
from trove.flavor import models
|
||||
@ -27,7 +29,7 @@ class FlavorController(wsgi.Controller):
|
||||
"""Return a single flavor."""
|
||||
context = req.environ[wsgi.CONTEXT_KEY]
|
||||
self._validate_flavor_id(id)
|
||||
flavor = models.Flavor(context=context, flavor_id=int(id))
|
||||
flavor = models.Flavor(context=context, flavor_id=id)
|
||||
# Pass in the request to build accurate links.
|
||||
return wsgi.Result(views.FlavorView(flavor, req).data(), 200)
|
||||
|
||||
@ -38,6 +40,8 @@ class FlavorController(wsgi.Controller):
|
||||
return wsgi.Result(views.FlavorsView(flavors, req).data(), 200)
|
||||
|
||||
def _validate_flavor_id(self, id):
|
||||
if isinstance(id, six.string_types):
|
||||
return
|
||||
try:
|
||||
if int(id) != float(id):
|
||||
raise exception.NotFound(uuid=id)
|
||||
|
@ -28,11 +28,19 @@ class FlavorView(object):
|
||||
|
||||
def data(self):
|
||||
|
||||
# If the flavor id cannot be cast to an int, we simply return
|
||||
# no id and rely on str_id instead.
|
||||
try:
|
||||
f_id = int(self.flavor.id)
|
||||
except ValueError:
|
||||
f_id = None
|
||||
|
||||
flavor = {
|
||||
'id': int(self.flavor.id),
|
||||
'id': f_id,
|
||||
'links': self._build_links(),
|
||||
'name': self.flavor.name,
|
||||
'ram': self.flavor.ram,
|
||||
'str_id': str(self.flavor.id),
|
||||
}
|
||||
|
||||
if not CONF.trove_volume_support and CONF.device_path is not None:
|
||||
|
@ -65,8 +65,14 @@ def assert_link_list_is_equal(flavor):
|
||||
assert_true(hasattr(flavor, 'links'))
|
||||
assert_true(flavor.links)
|
||||
|
||||
if flavor.id:
|
||||
flavor_id = str(flavor.id)
|
||||
else:
|
||||
flavor_id = flavor.str_id
|
||||
|
||||
for link in flavor.links:
|
||||
href = link['href']
|
||||
|
||||
if "self" in link['rel']:
|
||||
expected_href = os.path.join(test_config.dbaas_url, "flavors",
|
||||
str(flavor.id))
|
||||
@ -74,12 +80,12 @@ def assert_link_list_is_equal(flavor):
|
||||
msg = ("REL HREF %s doesn't start with %s" %
|
||||
(href, test_config.dbaas_url))
|
||||
assert_true(href.startswith(url), msg)
|
||||
url = os.path.join("flavors", str(flavor.id))
|
||||
msg = "REL HREF %s doesn't end in 'flavors/id'" % href
|
||||
url = os.path.join("flavors", flavor_id)
|
||||
msg = "REL HREF %s doesn't end in '%s'" % (href, url)
|
||||
assert_true(href.endswith(url), msg)
|
||||
elif "bookmark" in link['rel']:
|
||||
base_url = test_config.version_url.replace('http:', 'https:', 1)
|
||||
expected_href = os.path.join(base_url, "flavors", str(flavor.id))
|
||||
expected_href = os.path.join(base_url, "flavors", flavor_id)
|
||||
msg = 'bookmark "href" must be %s, not %s' % (expected_href, href)
|
||||
assert_equal(href, expected_href, msg)
|
||||
else:
|
||||
@ -139,7 +145,8 @@ class Flavors(object):
|
||||
|
||||
@test
|
||||
def test_flavor_list_attrs(self):
|
||||
allowed_attrs = ['id', 'name', 'ram', 'links', 'local_storage']
|
||||
allowed_attrs = ['id', 'name', 'ram', 'links', 'local_storage',
|
||||
'str_id']
|
||||
flavors = self.rd_client.flavors.list()
|
||||
attrcheck = AttrCheck()
|
||||
for flavor in flavors:
|
||||
@ -151,7 +158,8 @@ class Flavors(object):
|
||||
|
||||
@test
|
||||
def test_flavor_get_attrs(self):
|
||||
allowed_attrs = ['id', 'name', 'ram', 'links', 'local_storage']
|
||||
allowed_attrs = ['id', 'name', 'ram', 'links', 'local_storage',
|
||||
'str_id']
|
||||
flavor = self.rd_client.flavors.get(1)
|
||||
attrcheck = AttrCheck()
|
||||
flavor_dict = flavor._info
|
||||
@ -163,4 +171,4 @@ class Flavors(object):
|
||||
@test
|
||||
def test_flavor_not_found(self):
|
||||
assert_raises(exceptions.NotFound,
|
||||
self.rd_client.flavors.get, "detail")
|
||||
self.rd_client.flavors.get, "foo")
|
||||
|
@ -381,6 +381,18 @@ class CreateInstanceFail(object):
|
||||
|
||||
self.delete_async(result.id)
|
||||
|
||||
def test_create_failure_with_empty_flavor(self):
|
||||
instance_name = "instance-failure-with-empty-flavor"
|
||||
databases = []
|
||||
if VOLUME_SUPPORT:
|
||||
volume = {'size': 1}
|
||||
else:
|
||||
volume = None
|
||||
assert_raises(exceptions.BadRequest, dbaas.instances.create,
|
||||
instance_name, '',
|
||||
volume, databases)
|
||||
assert_equal(400, dbaas.last_http_code)
|
||||
|
||||
@test(enabled=VOLUME_SUPPORT)
|
||||
def test_create_failure_with_empty_volume(self):
|
||||
instance_name = "instance-failure-with-no-volume-size"
|
||||
@ -694,6 +706,54 @@ class CreateInstance(object):
|
||||
check.volume()
|
||||
|
||||
|
||||
@test(depends_on_classes=[InstanceSetup],
|
||||
groups=[GROUP, tests.INSTANCES],
|
||||
runs_after_groups=[tests.PRE_INSTANCES])
|
||||
class CreateInstanceFlavors(object):
|
||||
def _result_is_active(self):
|
||||
instance = dbaas.instances.get(self.result.id)
|
||||
if instance.status == "ACTIVE":
|
||||
return True
|
||||
else:
|
||||
# If its not ACTIVE, anything but BUILD must be
|
||||
# an error.
|
||||
assert_equal("BUILD", instance.status)
|
||||
if instance_info.volume is not None:
|
||||
assert_equal(instance.volume.get('used', None), None)
|
||||
return False
|
||||
|
||||
def _delete_async(self, instance_id):
|
||||
dbaas.instances.delete(instance_id)
|
||||
while True:
|
||||
try:
|
||||
dbaas.instances.get(instance_id)
|
||||
except exceptions.NotFound:
|
||||
return True
|
||||
time.sleep(1)
|
||||
|
||||
def _create_with_flavor(self, flavor_id):
|
||||
if not FAKE:
|
||||
raise SkipTest("This test only for fake mode.")
|
||||
instance_name = "instance-with-flavor-%s" % flavor_id
|
||||
databases = []
|
||||
if VOLUME_SUPPORT:
|
||||
volume = {'size': 1}
|
||||
else:
|
||||
volume = None
|
||||
self.result = dbaas.instances.create(instance_name, flavor_id, volume,
|
||||
databases)
|
||||
poll_until(self._result_is_active)
|
||||
self._delete_async(self.result.id)
|
||||
|
||||
@test
|
||||
def test_create_with_int_flavor(self):
|
||||
self._create_with_flavor(1)
|
||||
|
||||
@test
|
||||
def test_create_with_str_flavor(self):
|
||||
self._create_with_flavor('custom')
|
||||
|
||||
|
||||
@test(depends_on_classes=[InstanceSetup], groups=[GROUP_NEUTRON])
|
||||
class CreateInstanceWithNeutron(unittest.TestCase):
|
||||
@time_out(TIMEOUT_INSTANCE_CREATE)
|
||||
|
@ -131,7 +131,7 @@ class MalformedJson(object):
|
||||
|
||||
poll_until(_check_instance_status)
|
||||
try:
|
||||
self.dbaas.instances.resize_instance(self.instance.id, "bad data")
|
||||
self.dbaas.instances.resize_instance(self.instance.id, "")
|
||||
except Exception as e:
|
||||
resp, body = self.dbaas.client.last_response
|
||||
httpCode = resp.status
|
||||
|
@ -69,6 +69,7 @@ class FakeFlavors(object):
|
||||
self._add(10, 2, "m1.rd-tiny", 512)
|
||||
self._add(11, 0, "eph.rd-tiny", 512, 1)
|
||||
self._add(12, 20, "eph.rd-smaller", 768, 2)
|
||||
self._add("custom", 25, "custom.small", 512, 1)
|
||||
# self._add(13, 20, "m1.heat", 512)
|
||||
|
||||
def _add(self, *args, **kwargs):
|
||||
@ -76,7 +77,11 @@ class FakeFlavors(object):
|
||||
self.db[new_flavor.id] = new_flavor
|
||||
|
||||
def get(self, id):
|
||||
id = int(id)
|
||||
try:
|
||||
id = int(id)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
if id not in self.db:
|
||||
raise nova_exceptions.NotFound(404, "Flavor id not found %s" % id)
|
||||
return self.db[id]
|
||||
@ -617,7 +622,7 @@ class FakeHost(object):
|
||||
"""
|
||||
self.instances = []
|
||||
self.percentUsed = 0
|
||||
self.totalRAM = 2004 # 16384
|
||||
self.totalRAM = 32000 # 16384
|
||||
self.usedRAM = 0
|
||||
for server in self.servers.list():
|
||||
print(server)
|
||||
@ -629,11 +634,11 @@ class FakeHost(object):
|
||||
'name': server.name,
|
||||
'status': server.status
|
||||
})
|
||||
try:
|
||||
flavor = FLAVORS.get(server.flavor_ref)
|
||||
except ValueError:
|
||||
# Maybe flavor_ref isn't an int?
|
||||
if (str(server.flavor_ref).startswith('http:') or
|
||||
str(server.flavor_ref).startswith('https:')):
|
||||
flavor = FLAVORS.get_by_href(server.flavor_ref)
|
||||
else:
|
||||
flavor = FLAVORS.get(server.flavor_ref)
|
||||
ram = flavor.ram
|
||||
self.usedRAM += ram
|
||||
decimal = float(self.usedRAM) / float(self.totalRAM)
|
||||
|
@ -194,6 +194,12 @@ class TestInstanceController(TestCase):
|
||||
validator = jsonschema.Draft4Validator(schema)
|
||||
self.assertTrue(validator.is_valid(body))
|
||||
|
||||
def test_validate_resize_instance_string(self):
|
||||
body = {"resize": {"flavorRef": 'foo'}}
|
||||
schema = self.controller.get_schema('action', body)
|
||||
validator = jsonschema.Draft4Validator(schema)
|
||||
self.assertTrue(validator.is_valid(body))
|
||||
|
||||
def test_validate_resize_instance_empty_url(self):
|
||||
body = {"resize": {"flavorRef": ""}}
|
||||
schema = self.controller.get_schema('action', body)
|
||||
@ -202,10 +208,7 @@ class TestInstanceController(TestCase):
|
||||
errors = sorted(validator.iter_errors(body), key=lambda e: e.path)
|
||||
self.verify_errors(errors[0].context,
|
||||
["'' is too short",
|
||||
"'' does not match 'http[s]?://(?:[a-zA-Z]"
|
||||
"|[0-9]|[$-_@.&+]|[!*\\\\(\\\\),]"
|
||||
"|(?:%[0-9a-fA-F][0-9a-fA-F]))+'",
|
||||
"'' does not match '[0-9]+'",
|
||||
"'' does not match '^.*[0-9a-zA-Z]+.*$'",
|
||||
"'' is not of type 'integer'"],
|
||||
["flavorRef", "flavorRef", "flavorRef",
|
||||
"flavorRef"],
|
||||
|
Loading…
x
Reference in New Issue
Block a user