Merge "Implement flavors"

This commit is contained in:
Jenkins 2017-01-27 17:53:11 +00:00 committed by Gerrit Code Review
commit fb898bf75f
26 changed files with 512 additions and 434 deletions

View File

@ -1,12 +0,0 @@
{
"criteria": [
{
"name": "cpu",
"description": "Generates cpu based flavors"
},
{
"name": "default",
"description": "Generates 3 flavors(Tiny, Medium, Large) for each node considering all cpu cores, ram and storage"
}
]
}

View File

@ -1,3 +1,36 @@
{
}
[
{
"created_at": "2017-01-19 18:46:30 UTC",
"name": "test",
"properties": {
"memory": [
{
"capacity_mib": "3000",
"type": "DDR3"
}
],
"processor": [
{
"total_cores": "10"
}
]
},
"updated_at": "2017-01-19 18:46:30 UTC",
"uuid": "33d07db6-82c1-48ac-abca-2761433b79f9"
},
{
"created_at": "2017-01-19 18:49:45 UTC",
"name": "test 2",
"properties": {
"memory": {
"capacity_mib": "1000"
},
"processor": {
"model": "Intel",
"total_cores": "2"
}
},
"updated_at": "2017-01-19 18:49:45 UTC",
"uuid": "dd561046-4372-40df-ad34-8f8c65d50e02"
}
]

View File

@ -1,8 +1,12 @@
[
[
"[{\"flavor\": {\"disk\": 0, \"vcpus\": 0, \"ram\": 16, \"name\": \"S_irsd-Rack1Block1\", \"id\": \"321a271b-ab30-4dfb-a098-6cfb8549a143\"}}, {\"extra_specs\": {\"Rack\": \"1\", \"Block\": \"1\"}}]",
"[{\"flavor\": {\"disk\": 0, \"vcpus\": 1, \"ram\": 32, \"name\": \"M_irsd-Rack1Block1\", \"id\": \"819ba7e5-1621-4bf1-b904-9a1a433fd338\"}}, {\"extra_specs\": {\"Rack\": \"1\", \"Block\": \"1\"}}]",
"[{\"flavor\": {\"disk\": 0, \"vcpus\": 2, \"ram\": 64, \"name\": \"L_irsd-Rack1Block1\", \"id\": \"79e27bb9-2a7e-4c10-8ded-9ec4cdd4856d\"}}, {\"extra_specs\": {\"Rack\": \"1\", \"Block\": \"1\"}}]"
]
]
{
"name": "test",
"properties": {
"memory": {
"capacity_mib": "3000"
},
"processor": {
"total_cores": "10",
"model": "Intel"
}
}
}

View File

@ -1,4 +1,12 @@
{
"criteria": "cpu, storage"
"name": "test",
"properties": {
"memory": {
"capacity_mib": "3000"
},
"processor": {
"total_cores": "10",
"model": "Intel"
}
}
}

View File

@ -63,17 +63,35 @@ created_at:
in: body
required: true
type: string
criteria_list:
flavor_uuid:
description: |
Criteria name for generated a new one.
UUID for flavor.
in: body
required: true
required: false
type: string
criteria_object:
flavor_name:
description: |
Criteria object including name and its description.
Name for specified flavor.
in: body
required: true
required: false
type: string
flavor_ram:
description: |
RAM requirement for flavor.
in: body
required: false
type: string
flavor_processor_model:
description: |
Processor model specified by flavor.
in: body
required: false
type: string
flavor_cores:
description: |
Number of processor cores specified by flavor.
in: body
required: false
type: string
id:
description: |

View File

@ -7,13 +7,10 @@ Flavors
List, Searching of Flavors through the ``/v1/flavors``
List Flavor
List Flavors
============
.. rest_method:: GET /v1/flavor/
Leaving this empty for discussion due to there isn't a DB to keep generated flavor.
.. rest_method:: GET /v1/flavors/
Normal response codes: 200
@ -32,8 +29,8 @@ Response
:language: javascript
Generate Flavor
===============
Create Flavor
=============
.. rest_method:: POST /v1/flavors
@ -46,7 +43,10 @@ Request
.. rest_parameters:: parameters.yaml
- criterial: criteria_list
- name: flavor_name
- ram: flavor_ram
- processor_model: flavor_processor_model
- cores: flavor_cores
**Example generate flavor :**
@ -61,30 +61,49 @@ Response
.. literalinclude:: mockup/flavor-post-response.json
:language: javascript
List Flavor criteria
=====================
Update Flavor
=============
.. rest_method:: GET /v1/flavors/criteria
.. rest_method:: PATCH /v1/flavors/{flavor_uuid}
Get all supported flavor generation criteria along with their description.
Updates the information stored about a flavor.
Normal response codes: 200
Error response codes: unauthorized(401), forbidden(403)
Error response codes: badRequest(400), unauthorized(401), forbidden(403), 404
Request
-------
.. rest_parameters:: parameters.yaml
- flavor_uuid: flavor_uuid
Response
--------
.. rest_parameters:: parameters.yaml
- criteria: criteria_object
- uuid: flavor_uuid
- name: flavor_name
- ram: flavor_ram
- processor_model: flavor_processor_model
- cores: flavor_cores
**Example JSON representation of a Compute System:**
Delete Flavor
=============
.. literalinclude:: mockup/flavor-criteria-get-response.json
:language: javascript
.. rest_method:: DELETE /v1/flavors/{flavor_uuid}
Deletes a flavor.
Normal response codes: 204
Error response codes: 401, 403, 404, 409
Request
-------
.. rest_parameters:: parameters.yaml
- flavor_ident: flavor_ident

View File

@ -80,7 +80,8 @@ api.add_resource(v1_systems.Systems, '/v1/systems/<string:systemid>',
# Flavor(s) operations
api.add_resource(v1_flavors.Flavors, '/v1/flavors', endpoint='flavors')
api.add_resource(v1_flavors.Flavors, '/v1/flavors/<string:flavorid>',
endpoint='flavor')
# Storage(s) operations
api.add_resource(v1_storages.StoragesList, '/v1/storages', endpoint='storages')

View File

@ -16,8 +16,10 @@ import logging
from flask import request
from flask_restful import Resource
from six.moves import http_client
from valence.flavors import flavors
from valence.common import utils
from valence.controller import flavors
LOG = logging.getLogger(__name__)
@ -25,7 +27,17 @@ LOG = logging.getLogger(__name__)
class Flavors(Resource):
def get(self):
return flavors.get_available_criteria()
return utils.make_response(http_client.OK, flavors.list_flavors())
def post(self):
return flavors.create_flavors(request.get_json())
return utils.make_response(http_client.OK,
flavors.create_flavor(request.get_json()))
def delete(self, flavorid):
return utils.make_response(http_client.OK,
flavors.delete_flavor(flavorid))
def patch(self, flavorid):
return utils.make_response(http_client.OK,
flavors.update_flavor(flavorid,
request.get_json()))

View File

@ -13,15 +13,27 @@
# under the License.
import logging
from valence.flavors.generatorbase import generatorbase
from valence.db import api as db_api
LOG = logging.getLogger(__name__)
class exampleGenerator(generatorbase):
def __init__(self, nodes):
generatorbase.__init__(self, nodes)
def list_flavors():
flavor_models = db_api.Connection.list_flavors()
return [flavor.as_dict() for flavor in flavor_models]
def generate(self):
LOG.info("Example Flavor Generate")
return {"Info": "Example Flavor Generator- Not Yet Implemented"}
def create_flavor(values):
flavor = db_api.Connection.create_flavor(values)
return flavor.as_dict()
def delete_flavor(flavorid):
db_api.Connection.delete_flavor(flavorid)
return "Deleted flavor {0}".format(flavorid)
def update_flavor(flavorid, values):
flavor = db_api.Connection.update_flavor(flavorid, values)
return flavor.as_dict()

View File

@ -71,3 +71,47 @@ class Connection(object):
:returns: A list of all pod managers.
"""
return cls.dbdriver.list_podmanager()
@classmethod
def create_flavor(cls, values):
"""Create a new flavor.
:param values: The properties of the new flavor.
:returns: The created flavor.
"""
return cls.dbdriver.create_flavor(values)
@classmethod
def get_flavor_by_uuid(cls, flavor_uuid):
"""Get specific flavor by its uuid.
:param flavor_uuid: The uuid of the flavor.
:returns: The flavor with the specified uuid.
"""
return cls.dbdriver.get_flavor_by_uuid(flavor_uuid)
@classmethod
def delete_flavor(cls, flavor_uuid):
"""Delete a flavor by its uuid.
:param flavor_uuid: The uuid of the flavor to delete.
"""
cls.dbdriver.delete_flavor(flavor_uuid)
@classmethod
def update_flavor(cls, flavor_uuid, values):
"""Update properties of a specified flavor.
:param flavor_uuid: The uuid of the flavor to update.
:param values: The properties to be updated.
:returns: The updated flavor.
"""
return cls.dbdriver.update_flavor(flavor_uuid, values)
@classmethod
def list_flavors(cls):
"""Get a list of all flavors.
:returns: A list of all flavors.
"""
return cls.dbdriver.list_flavors()

View File

@ -19,7 +19,8 @@ from valence.db import models
etcd_directories = [
models.PodManager.path
models.PodManager.path,
models.Flavor.path
]
etcd_client = etcd.Client(config.etcd_host, config.etcd_port)

View File

@ -37,6 +37,8 @@ def translate_to_models(etcd_resp, model_type):
data = json.loads(etcd_resp.value)
if model_type == models.PodManager.path:
ret = models.PodManager(**data)
elif model_type == models.Flavor.path:
ret = models.Flavor(**data)
else:
# TODO(lin.a.yang): after exception module got merged, raise
# valence specific InvalidParameter exception here
@ -102,3 +104,48 @@ class EtcdDriver(object):
podm, models.PodManager.path))
return podmanagers
def get_flavor_by_uuid(self, flavor_uuid):
try:
resp = self.client.read(models.Flavor.etcd_path(flavor_uuid))
except etcd.EtcdKeyNotFound:
# TODO(ntpttr): Change this to a valence specific exception
# when the exceptions module is merged.
raise Exception('Flavor {0} not found.'.format(flavor_uuid))
return translate_to_models(resp, models.Flavor.path)
def create_flavor(self, values):
values['uuid'] = uuidutils.generate_uuid()
flavor = models.Flavor(**values)
flavor.save()
return flavor
def delete_flavor(self, flavor_uuid):
flavor = self.get_flavor_by_uuid(flavor_uuid)
flavor.delete()
def update_flavor(self, flavor_uuid, values):
flavor = self.get_flavor_by_uuid(flavor_uuid)
flavor.update(values)
return flavor
def list_flavors(self):
try:
resp = getattr(self.client.read(models.Flavor.path),
'children', None)
except etcd.EtcdKeyNotFound:
LOG.error("Path '/flavors' does not exist, the etcd server may "
"not have been initialized appropriately.")
raise
flavors = []
for flavor in resp:
if flavor.value is not None:
flavors.append(translate_to_models(
flavor, models.Flavor.path))
return flavors

View File

@ -154,3 +154,38 @@ class PodManager(ModelBaseWithTimeStamp):
'validate': types.Text.validate
}
}
class Flavor(ModelBaseWithTimeStamp):
path = "/flavors"
fields = {
'uuid': {
'validate': types.Text.validate
},
'name': {
'validate': types.Text.validate
},
'properties': {
'memory': {
'capacity_mib': {
'validate': types.Text.validate
},
'type': {
'validate': types.Text.validate
},
'validate': types.Dict.validate
},
'processor': {
'total_cores': {
'validate': types.Text.validate
},
'model': {
'validate': types.Text.validate
},
'validate': types.Dict.validate
},
'validate': types.Dict.validate
}
}

View File

@ -1,56 +0,0 @@
# Copyright (c) 2016 Intel, Inc.
#
# 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 importlib import import_module
import logging
import os
from valence.redfish import redfish as rfs
FLAVOR_PLUGIN_PATH = os.path.dirname(os.path.abspath(__file__)) + '/plugins'
LOG = logging.getLogger(__name__)
def get_available_criteria():
pluginfiles = [f.split('.')[0]
for f in os.listdir(FLAVOR_PLUGIN_PATH)
if os.path.isfile(os.path.join(FLAVOR_PLUGIN_PATH, f))
and not f.startswith('__') and f.endswith('.py')]
resp = []
for filename in pluginfiles:
module = import_module("valence.flavors.plugins." + filename)
myclass = getattr(module, filename + 'Generator')
inst = myclass([])
resp.append({'name': filename, 'description': inst.description()})
return {'criteria': resp}
def create_flavors(data):
"""criteria : comma separated generator names
This should be same as their file name)
"""
criteria = data["criteria"]
respjson = []
lst_systems = rfs.systems_list()
for criteria_name in criteria.split(","):
if criteria_name:
LOG.info("Calling generator : %s ." % criteria_name)
module = __import__("valence.flavors.plugins." + criteria_name,
fromlist=["*"])
classobj = getattr(module, criteria_name + "Generator")
inst = classobj(lst_systems)
respjson.append(inst.generate())
return respjson

View File

@ -1,37 +0,0 @@
# Copyright (c) 2016 Intel, Inc.
#
# 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 json
import uuid
class generatorbase(object):
def __init__(self, nodes):
self.nodes = nodes
self.prepend_name = 'irsd-'
def description(self):
return "Description of plugins"
def _flavor_template(self, name, ram, cpus, disk, extraspecs):
return json.dumps([{"flavor":
{"name": name,
"ram": int(ram),
"vcpus": int(cpus),
"disk": int(disk),
"id": str(uuid.uuid4())}},
{"extra_specs": extraspecs}])
def generate(self):
raise NotImplementedError()

View File

@ -1,5 +0,0 @@
"""from os.path import dirname, basename, isfile
import glob
modules = glob.glob(dirname(__file__)+"/*.py")
__all__ = [ basename(f)[:-3] for f in modules if isfile(f)]
"""

View File

@ -1,55 +0,0 @@
# Copyright (c) 2016 Intel, Inc.
#
# 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
import re
from valence.flavors.generatorbase import generatorbase
LOG = logging.getLogger()
class assettagGenerator(generatorbase):
def __init__(self, nodes):
generatorbase.__init__(self, nodes)
def description(self):
return "Demo only: Generates location based on assettag"
def generate(self):
LOG.info("Default Generator")
for node in self.nodes:
LOG.info("Node ID " + node['id'])
location = node['location']
location = location.split('Sled')[0]
location_lst = re.split("(\d+)", location)
LOG.info(str(location_lst))
location_lst = list(filter(None, location_lst))
LOG.info(str(location_lst))
extraspecs = {location_lst[i]: location_lst[i + 1]
for i in range(0, len(location_lst), 2)}
name = self.prepend_name + location
return [
self._flavor_template("L_" + name,
node['ram'],
node['cpu']["count"],
node['storage'], extraspecs),
self._flavor_template("M_" + name,
int(node['ram']) / 2,
int(node['cpu']["count"]) / 2,
int(node['storage']) / 2, extraspecs),
self._flavor_template("S_" + name,
int(node['ram']) / 4,
int(node['cpu']["count"]) / 4,
int(node['storage']) / 4, extraspecs)
]

View File

@ -1,56 +0,0 @@
# Copyright (c) 2016 Intel, Inc.
#
# 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 valence.flavors.generatorbase import generatorbase
LOG = logging.getLogger(__name__)
class defaultGenerator(generatorbase):
def __init__(self, nodes):
generatorbase.__init__(self, nodes)
def description(self):
return ("Generates 3 flavors(Tiny, Medium, Large) for "
"each node considering all cpu cores, ram and storage")
def generate(self):
LOG.info("Default Generator")
for node in self.nodes:
LOG.debug("Node ID " + node['id'])
location = node['location']
LOG.debug(location)
location_lst = location.split("_")
location_lst = list(filter(None, location_lst))
extraspecs = ({l[0]: l[1]
for l in (l.split(":") for l in location_lst)})
name = self.prepend_name + node['id']
return [
self._flavor_template("L_" + name,
node['ram'],
node['cpu']["count"],
node['storage'],
extraspecs),
self._flavor_template("M_" + name,
int(node['ram']) / 2,
int(node['cpu']["count"]) / 2,
int(node['storage']) / 2,
extraspecs),
self._flavor_template("S_" + name,
int(node['ram']) / 4,
int(node['cpu']["count"]) / 4,
int(node['storage']) / 4,
extraspecs)
]

View File

@ -0,0 +1,47 @@
# 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 unittest import TestCase
import mock
from valence.controller import flavors
from valence.tests.unit.fakes import flavor_fakes as fakes
class TestFlavors(TestCase):
@mock.patch('valence.db.api.Connection.list_flavors')
def test_list_flavors(self, mock_db_list_flavors):
mock_db_list_flavors.return_value = fakes.fake_flavor_model_list()
result = flavors.list_flavors()
self.assertEqual(fakes.fake_flavor_list(), result)
@mock.patch('valence.db.api.Connection.create_flavor')
def test_create_flavor(self, mock_db_create_flavor):
mock_db_create_flavor.return_value = fakes.fake_flavor_model()
result = flavors.create_flavor(fakes.fake_flavor())
self.assertEqual(fakes.fake_flavor(), result)
@mock.patch('valence.db.api.Connection.delete_flavor')
def test_delete_flavor(self, mock_db_delete_flavor):
expected = "Deleted flavor 00000000-0000-0000-0000-000000000000"
result = flavors.delete_flavor("00000000-0000-0000-0000-000000000000")
self.assertEqual(expected, result)
@mock.patch('valence.db.api.Connection.update_flavor')
def test_update_flavor(self, mock_db_update_flavor):
mock_db_update_flavor.return_value = fakes.fake_flavor_model()
result = flavors.update_flavor(
"00000000-0000-0000-0000-00000000",
{"name": "Flavor 1"})
self.assertEqual(fakes.fake_flavor(), result)

View File

@ -44,6 +44,23 @@ class TestDBAPI(unittest.TestCase):
'/pod_managers/' + podmanager['uuid'],
json.dumps(result.as_dict()))
@freezegun.freeze_time('2017-01-01')
@mock.patch('etcd.Client.write')
@mock.patch('etcd.Client.read')
def test_create_flavor(self, mock_etcd_read, mock_etcd_write):
flavor = utils.get_test_flavor()
fake_utcnow = '2017-01-01 00:00:00 UTC'
flavor['created_at'] = fake_utcnow
flavor['updated_at'] = fake_utcnow
mock_etcd_read.side_effect = etcd.EtcdKeyNotFound
result = db_api.Connection.create_flavor(flavor)
self.assertEqual(flavor, result.as_dict())
mock_etcd_read.assert_called_with('/flavors/' + flavor['uuid'])
mock_etcd_write.assert_called_with('/flavors/' + flavor['uuid'],
json.dumps(result.as_dict()))
@mock.patch('etcd.Client.read')
def test_get_podmanager_by_uuid(self, mock_etcd_read):
podmanager = utils.get_test_podmanager()
@ -56,6 +73,18 @@ class TestDBAPI(unittest.TestCase):
mock_etcd_read.assert_called_with(
'/pod_managers/' + podmanager['uuid'])
@mock.patch('etcd.Client.read')
def test_get_flavor_by_uuid(self, mock_etcd_read):
flavor = utils.get_test_flavor()
mock_etcd_read.return_value = utils.get_etcd_read_result(
flavor['uuid'], json.dumps(flavor))
result = db_api.Connection.get_flavor_by_uuid(flavor['uuid'])
self.assertEqual(flavor, result.as_dict())
mock_etcd_read.assert_called_with(
'/flavors/' + flavor['uuid'])
@mock.patch('etcd.Client.read')
def test_get_podmanager_not_found(self, mock_etcd_read):
podmanager = utils.get_test_podmanager()
@ -69,6 +98,18 @@ class TestDBAPI(unittest.TestCase):
mock_etcd_read.assert_called_with(
'/pod_managers/' + podmanager['uuid'])
@mock.patch('etcd.Client.read')
def test_get_flavor_not_found(self, mock_etcd_read):
flavor = utils.get_test_flavor()
mock_etcd_read.side_effect = etcd.EtcdKeyNotFound
with self.assertRaises(Exception) as context: # noqa: H202
db_api.Connection.get_flavor_by_uuid(flavor['uuid'])
self.assertTrue('Flavor {0} not found.'.format(
flavor['uuid']) in str(context.exception))
mock_etcd_read.assert_called_with('/flavors/' + flavor['uuid'])
@mock.patch('etcd.Client.delete')
@mock.patch('etcd.Client.read')
def test_delete_podmanager(self, mock_etcd_read, mock_etcd_delete):
@ -81,6 +122,17 @@ class TestDBAPI(unittest.TestCase):
mock_etcd_delete.assert_called_with(
'/pod_managers/' + podmanager['uuid'])
@mock.patch('etcd.Client.delete')
@mock.patch('etcd.Client.read')
def test_delete_flavor(self, mock_etcd_read, mock_etcd_delete):
flavor = utils.get_test_flavor()
mock_etcd_read.return_value = utils.get_etcd_read_result(
flavor['uuid'], json.dumps(flavor))
db_api.Connection.delete_flavor(flavor['uuid'])
mock_etcd_delete.assert_called_with('/flavors/' + flavor['uuid'])
@freezegun.freeze_time("2017-01-01")
@mock.patch('etcd.Client.write')
@mock.patch('etcd.Client.read')
@ -103,3 +155,26 @@ class TestDBAPI(unittest.TestCase):
mock_etcd_write.assert_called_with(
'/pod_managers/' + podmanager['uuid'],
json.dumps(result.as_dict()))
@freezegun.freeze_time("2017-01-01")
@mock.patch('etcd.Client.write')
@mock.patch('etcd.Client.read')
def test_update_flavor(self, mock_etcd_read, mock_etcd_write):
flavor = utils.get_test_flavor()
mock_etcd_read.return_value = utils.get_etcd_read_result(
flavor['uuid'], json.dumps(flavor))
fake_utcnow = '2017-01-01 00:00:00 UTC'
flavor['updated_at'] = fake_utcnow
flavor.update({'properties': {'memory': {'type': 'new_type'}}})
result = db_api.Connection.update_flavor(
flavor['uuid'], {'properties': {'memory': {'type': 'new_type'}}})
self.assertEqual(flavor, result.as_dict())
mock_etcd_read.assert_called_with(
'/flavors/' + flavor['uuid'])
mock_etcd_write.assert_called_with(
'/flavors/' + flavor['uuid'],
json.dumps(result.as_dict()))

View File

@ -56,3 +56,22 @@ def get_test_podmanager(**kwargs):
'created_at': kwargs.get('created_at', '2016-01-01 00:00:00 UTC'),
'updated_at': kwargs.get('updated_at', '2016-01-01 00:00:00 UTC'),
}
def get_test_flavor(**kwargs):
return {
'uuid': kwargs.get('uuid', 'f0565d8c-d79b-11e6-bf26-cec0c932ce01'),
'name': kwargs.get('name', 'fake_name'),
'properties': {
'memory': {
'capacity_mib': kwargs.get('capacity_mib', 'fake_capacity'),
'type': kwargs.get('type', 'fake_type'),
},
'processor': {
'total_cores': kwargs.get('total_cores', 'fake_cores'),
'model': kwargs.get('model', 'fake_model')
}
},
'created_at': kwargs.get('created_at', '2016-01-01 00:00:00 UTC'),
'updated_at': kwargs.get('updated_at', '2016-01-01 00:00:00 UTC'),
}

View File

@ -0,0 +1,89 @@
# 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 valence.db import models
def fake_flavor():
return {
"uuid": "00000000-0000-0000-0000-000000000000",
"name": "Flavor 1",
"properties": {
"memory": {
"capacity_mib": "1000",
"type": "DDR2"
},
"processor": {
"total_cores": "10",
"model": "Intel"
}
}
}
def fake_flavor_model():
return models.Flavor(**fake_flavor())
def fake_flavor_list():
return [
{
"uuid": "00000000-0000-0000-0000-000000000000",
"name": "Flavor 1",
"properties": {
"memory": {
"capacity_mib": "1000",
"type": "DDR2"
},
"processor": {
"total_cores": "10",
"model": "Intel"
}
}
},
{
"uuid": "11111111-1111-1111-1111-111111111111",
"name": "Flavor 2",
"properties": {
"memory": {
"capacity_mib": "2000",
"type": "DDR3"
},
"processor": {
"total_cores": "20",
"model": "Intel"
}
}
},
{
"uuid": "22222222-2222-2222-2222-222222222222",
"name": "Flavor 3",
"properties": {
"memory": {
"capacity_mib": "3000",
"type": "SDRAM"
},
"processor": {
"total_cores": "30",
"model": "Intel"
}
}
}
]
def fake_flavor_model_list():
values_list = fake_flavor_list()
for i in range(len(values_list)):
values_list[i] = models.Flavor(**values_list[i])
return values_list

View File

@ -1,76 +0,0 @@
# 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 json
def fake_flavor_nodes():
return [
{"id": '1', "cpu": {'count': 2},
"ram": 1024, "storage": 256,
"nw": 'nw1', "location": 'location:1',
"uuid": 'fe542581-97fe-4dbb-a1da'
},
{"id": '2', "cpu": {'count': 4},
"ram": 2048, "storage": 500,
"nw": 'nw2', "location": 'location:2',
"uuid": 'f0f96c58-d3d0-4292-a191'
}
]
def fake_assettag_flavors():
return [json.dumps([{"flavor":
{"name": "L_irsd-location:2",
"ram": 2048,
"vcpus": 4,
"disk": 500,
"id": "f0f96c58-d3d0-4292-a191"}},
{"extra_specs": {"location:": "2"}}]),
json.dumps([{"flavor":
{"name": "M_irsd-location:2",
"ram": 1024,
"vcpus": 2,
"disk": 250,
"id": "f0f96c58-d3d0-4292-a191"}},
{"extra_specs": {"location:": "2"}}]),
json.dumps([{"flavor":
{"name": "S_irsd-location:2",
"ram": 512,
"vcpus": 1,
"disk": 125,
"id": "f0f96c58-d3d0-4292-a191"}},
{"extra_specs": {"location:": "2"}}])]
def fake_default_flavors():
return [json.dumps([{"flavor":
{"name": "L_irsd-2",
"ram": 2048,
"vcpus": 4,
"disk": 500,
"id": "f0f96c58-d3d0-4292-a191"}},
{"extra_specs": {"location": "2"}}]),
json.dumps([{"flavor":
{"name": "M_irsd-2",
"ram": 1024,
"vcpus": 2,
"disk": 250,
"id": "f0f96c58-d3d0-4292-a191"}},
{"extra_specs": {"location": "2"}}]),
json.dumps([{"flavor":
{"name": "S_irsd-2",
"ram": 512,
"vcpus": 1,
"disk": 125,
"id": "f0f96c58-d3d0-4292-a191"}},
{"extra_specs": {"location": "2"}}])]

View File

@ -1,89 +0,0 @@
# 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 mock
import unittest
from valence.flavors import flavors
from valence.tests.unit.fakes import flavors_fakes as fakes
class TestFlavors(unittest.TestCase):
def test_get_available_criteria(self):
expected = {'criteria': [{'name': 'default',
'description': 'Generates 3 flavors(Tiny, '
'Medium, Large) for each '
'node considering all cpu '
'cores, ram and storage'},
{'name': 'assettag',
'description': 'Demo only: Generates '
'location based on assettag'},
{'name': 'example',
'description': 'Description of plugins'}]}
result = flavors.get_available_criteria()
expected = sorted(expected['criteria'], key=lambda x: x['name'])
result = sorted(result['criteria'], key=lambda x: x['name'])
self.assertEqual(expected, result)
@mock.patch(
'valence.flavors.plugins.assettag.assettagGenerator.generate')
@mock.patch('uuid.uuid4')
@mock.patch('valence.redfish.redfish.systems_list')
def test_create_flavors_asserttag(self, mock_systems,
mock_uuid,
mock_generate):
fake_systems = fakes.fake_flavor_nodes()
mock_systems.return_value = fake_systems
mock_uuid.return_value = 'f0f96c58-d3d0-4292-a191'
mock_generate.return_value = fakes.fake_assettag_flavors()
result = flavors.create_flavors(data={"criteria": "assettag"})
expected = [fakes.fake_assettag_flavors()]
self.assertEqual(expected, result)
@mock.patch(
'valence.flavors.plugins.default.defaultGenerator.generate')
@mock.patch('uuid.uuid4')
@mock.patch('valence.redfish.redfish.systems_list')
def test_create_flavors_default(self, mock_systems,
mock_uuid,
mock_generate):
fake_systems = fakes.fake_flavor_nodes()
mock_systems.return_value = fake_systems
mock_uuid.return_value = 'f0f96c58-d3d0-4292-a191'
mock_generate.return_value = fakes.fake_default_flavors()
result = flavors.create_flavors(data={"criteria": "default"})
expected = [fakes.fake_default_flavors()]
self.assertEqual(expected, result)
@mock.patch(
'valence.flavors.plugins.default.defaultGenerator.generate')
@mock.patch(
'valence.flavors.plugins.assettag.assettagGenerator.generate')
@mock.patch('uuid.uuid4')
@mock.patch('valence.redfish.redfish.systems_list')
def test_create_flavors_asserttag_and_default(self, mock_systems,
mock_uuid,
mock_assettag_generate,
mock_default_generate):
fake_systems = fakes.fake_flavor_nodes()
mock_systems.return_value = fake_systems
mock_uuid.return_value = 'f0f96c58-d3d0-4292-a191'
mock_assettag_generate.return_value = \
fakes.fake_assettag_flavors()
mock_default_generate.return_value = \
fakes.fake_default_flavors()
result = flavors.create_flavors(
data={"criteria": "assettag,default"})
expected = [fakes.fake_assettag_flavors(),
fakes.fake_default_flavors()]
self.assertEqual(expected, result)