Merge "Implement flavors"
This commit is contained in:
commit
fb898bf75f
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
@ -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"
|
||||
}
|
||||
]
|
||||
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,12 @@
|
||||
{
|
||||
"criteria": "cpu, storage"
|
||||
"name": "test",
|
||||
"properties": {
|
||||
"memory": {
|
||||
"capacity_mib": "3000"
|
||||
},
|
||||
"processor": {
|
||||
"total_cores": "10",
|
||||
"model": "Intel"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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: |
|
||||
|
@ -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
|
||||
|
@ -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')
|
||||
|
@ -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()))
|
||||
|
@ -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()
|
@ -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()
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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
|
@ -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()
|
@ -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)]
|
||||
"""
|
@ -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)
|
||||
]
|
@ -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)
|
||||
]
|
47
valence/tests/unit/controller/test_flavors.py
Normal file
47
valence/tests/unit/controller/test_flavors.py
Normal 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)
|
@ -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()))
|
||||
|
@ -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'),
|
||||
}
|
||||
|
89
valence/tests/unit/fakes/flavor_fakes.py
Normal file
89
valence/tests/unit/fakes/flavor_fakes.py
Normal 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
|
@ -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"}}])]
|
@ -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)
|
Loading…
x
Reference in New Issue
Block a user