Add Flavor API

Change-Id: I26a0671d3a7431c52de898e3ea91c890dc3eb287
This commit is contained in:
bharath 2018-10-23 06:37:35 +05:30
parent 26e2299908
commit 06e72f347b
14 changed files with 867 additions and 4 deletions

View File

@ -22,6 +22,7 @@ import pecan
from gyan.api.controllers import base as controllers_base
from gyan.api.controllers import link
from gyan.api.controllers.v1 import hosts as host_controller
from gyan.api.controllers.v1 import flavors as flavor_controller
from gyan.api.controllers.v1 import ml_models as ml_model_controller
from gyan.api.controllers import versions as ver
from gyan.api import http_error
@ -59,7 +60,8 @@ class V1(controllers_base.APIBase):
'media_types',
'links',
'hosts',
'ml_models'
'ml_models',
'flavors'
)
@staticmethod
@ -81,6 +83,12 @@ class V1(controllers_base.APIBase):
pecan.request.host_url,
'hosts', '',
bookmark=True)]
v1.flavors = [link.make_link('self', pecan.request.host_url,
'flavors', ''),
link.make_link('bookmark',
pecan.request.host_url,
'flavors', '',
bookmark=True)]
v1.ml_models = [link.make_link('self', pecan.request.host_url,
'ml-models', ''),
link.make_link('bookmark',
@ -95,6 +103,7 @@ class Controller(controllers_base.Controller):
hosts = host_controller.HostController()
ml_models = ml_model_controller.MLModelController()
flavors = flavor_controller.FlavorController()
@pecan.expose('json')
def get(self):
@ -148,7 +157,7 @@ class Controller(controllers_base.Controller):
'method': pecan.request.method,
'body': pecan.request.body})
# LOG.debug(msg)
LOG.debug(args)
# LOG.debug(args)
return super(Controller, self)._route(args)

View File

@ -0,0 +1,210 @@
# 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 base64
import shlex
import json
from oslo_log import log as logging
from oslo_utils import strutils
from oslo_utils import uuidutils
import pecan
import six
from gyan.api.controllers import base
from gyan.api.controllers import link
from gyan.api.controllers.v1 import collection
from gyan.api.controllers.v1.schemas import flavors as schema
from gyan.api.controllers.v1.views import flavors_view as view
from gyan.api import utils as api_utils
from gyan.api import validation
from gyan.common import consts
from gyan.common import context as gyan_context
from gyan.common import exception
from gyan.common.i18n import _
from gyan.common.policies import flavor as policies
from gyan.common import policy
from gyan.common import utils
import gyan.conf
from gyan import objects
CONF = gyan.conf.CONF
LOG = logging.getLogger(__name__)
def check_policy_on_flavor(flavor, action):
context = pecan.request.context
policy.enforce(context, action, flavor, action=action)
class FlavorCollection(collection.Collection):
"""API representation of a collection of flavors."""
fields = {
'flavors',
'next'
}
"""A list containing flavor objects"""
def __init__(self, **kwargs):
super(FlavorCollection, self).__init__(**kwargs)
self._type = 'flavors'
@staticmethod
def convert_with_links(rpc_flavors, limit, url=None,
expand=False, **kwargs):
context = pecan.request.context
collection = FlavorCollection()
collection.flavors = \
[view.format_flavor(url, p)
for p in rpc_flavors]
collection.next = collection.get_next(limit, url=url, **kwargs)
return collection
class FlavorController(base.Controller):
"""Controller for Flavors."""
@pecan.expose('json')
@exception.wrap_pecan_controller_exception
def get_all(self, **kwargs):
"""Retrieve a list of flavors.
"""
context = pecan.request.context
policy.enforce(context, "flavor:get_all",
action="flavor:get_all")
return self._get_flavors_collection(**kwargs)
def _get_flavors_collection(self, **kwargs):
context = pecan.request.context
if utils.is_all_projects(kwargs):
policy.enforce(context, "flavor:get_all_all_projects",
action="flavor:get_all_all_projects")
context.all_projects = True
kwargs.pop('all_projects', None)
limit = api_utils.validate_limit(kwargs.pop('limit', None))
sort_dir = api_utils.validate_sort_dir(kwargs.pop('sort_dir', 'asc'))
sort_key = kwargs.pop('sort_key', 'id')
resource_url = kwargs.pop('resource_url', None)
expand = kwargs.pop('expand', None)
flavor_allowed_filters = ['name', 'cpu', 'python_version', 'driver',
'memory', 'disk', 'additional_details']
filters = {}
for filter_key in flavor_allowed_filters:
if filter_key in kwargs:
policy_action = policies.FLAVOR % ('get_one:' + filter_key)
context.can(policy_action, might_not_exist=True)
filter_value = kwargs.pop(filter_key)
filters[filter_key] = filter_value
marker_obj = None
marker = kwargs.pop('marker', None)
if marker:
marker_obj = objects.Flavor.get_by_uuid(context,
marker)
if kwargs:
unknown_params = [str(k) for k in kwargs]
msg = _("Unknown parameters: %s") % ", ".join(unknown_params)
raise exception.InvalidValue(msg)
flavors = objects.Flavor.list(context,
limit,
marker_obj,
sort_key,
sort_dir,
filters=filters)
return FlavorCollection.convert_with_links(flavors, limit,
url=resource_url,
expand=expand,
sort_key=sort_key,
sort_dir=sort_dir)
@pecan.expose('json')
@exception.wrap_pecan_controller_exception
def get_one(self, flavor_ident, **kwargs):
"""Retrieve information about the given flavor.
:param flavor_ident: UUID or name of a flavor.
"""
context = pecan.request.context
if utils.is_all_projects(kwargs):
policy.enforce(context, "flavor:get_one_all_projects",
action="flavor:get_one_all_projects")
context.all_projects = True
flavor = utils.get_flavor(flavor_ident)
check_policy_on_flavor(flavor.as_dict(), "flavor:get_one")
return view.format_flavor(pecan.request.host_url,
flavor)
@base.Controller.api_version("1.0")
@pecan.expose('json')
@api_utils.enforce_content_types(['application/json'])
@exception.wrap_pecan_controller_exception
@validation.validated(schema.flavor_create)
def post(self, **flavor_dict):
return self._do_post(**flavor_dict)
def _do_post(self, **flavor_dict):
"""Create or run a new flavor.
:param flavor_dict: a flavor within the request body.
"""
context = pecan.request.context
policy.enforce(context, "flavor:create",
action="flavor:create")
LOG.debug("bhaaaaaaaaaaaaaaaaaaaaaaaaaaa")
LOG.debug(flavor_dict)
flavor_dict["additional_details"] = json.dumps(flavor_dict["additional_details"])
LOG.debug(flavor_dict)
# flavor_dict["model_data"] = open("/home/bharath/model.zip", "rb").read()
new_flavor = objects.Flavor(context, **flavor_dict)
flavor = new_flavor.create(context)
LOG.debug(new_flavor)
# compute_api.flavor_create(context, new_flavor)
# Set the HTTP Location Header
pecan.response.location = link.build_url('flavors',
flavor.id)
pecan.response.status = 201
return view.format_flavor(pecan.request.host_url,
flavor)
@pecan.expose('json')
@exception.wrap_pecan_controller_exception
def patch(self, flavor_ident, **patch):
"""Update an existing flavor.
:param flavor_ident: UUID or name of a flavor.
:param patch: a json PATCH document to apply to this flavor.
"""
context = pecan.request.context
flavor = utils.get_flavor(flavor_ident)
check_policy_on_flavor(flavor.as_dict(), "flavor:update")
return view.format_flavor(context, pecan.request.host_url,
flavor.as_dict())
@pecan.expose('json')
@exception.wrap_pecan_controller_exception
@validation.validate_query_param(pecan.request, schema.query_param_delete)
def delete(self, flavor_ident, **kwargs):
"""Delete a flavor.
:param flavor_ident: UUID or Name of a Flavor.
:param force: If True, allow to force delete the Flavor.
"""
context = pecan.request.context
flavor = utils.get_flavor(flavor_ident)
check_policy_on_flavor(flavor.as_dict(), "flavor:delete")
flavor.destroy(context)
pecan.response.status = 204

View File

@ -0,0 +1,58 @@
# 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 copy
from gyan.api.controllers.v1.schemas import parameter_types
_flavor_properties = {}
flavor_create = {
'type': 'object',
'properties': {
"name": parameter_types.flavor_name,
"driver": parameter_types.flavor_driver,
"cpu": parameter_types.flavor_cpu,
"disk": parameter_types.flavor_disk,
'memory': parameter_types.flavor_memory,
'python_version': parameter_types.flavor_python_version,
'additional_details': parameter_types.flavor_additional_details
},
'required': ['name', 'cpu', 'memory', 'python_version', 'disk', 'driver', 'additional_details'],
'additionalProperties': False
}
query_param_create = {
'type': 'object',
'properties': {
'run': parameter_types.boolean_extended
},
'additionalProperties': False
}
ml_model_update = {
'type': 'object',
'properties': {},
'additionalProperties': False
}
query_param_delete = {
'type': 'object',
'properties': {
'force': parameter_types.boolean_extended,
'all_projects': parameter_types.boolean_extended,
'stop': parameter_types.boolean_extended
},
'additionalProperties': False
}

View File

@ -47,6 +47,50 @@ ml_model_name = {
'pattern': '^[a-zA-Z0-9][a-zA-Z0-9_.-]+$'
}
flavor_name = {
'type': ['string', 'null'],
'minLength': 2,
'maxLength': 255,
'pattern': '^[a-zA-Z0-9][a-zA-Z0-9_.-]+$'
}
flavor_cpu = {
'type': ['number', 'integer', 'null'],
'minLength': 2,
'maxLength': 255
}
flavor_driver = {
'type': ['string', 'null'],
'minLength': 2,
'maxLength': 255,
'pattern': '^[a-zA-Z0-9][a-zA-Z0-9_.-]+$'
}
flavor_disk = {
'type': ['string', 'null'],
'minLength': 2,
'maxLength': 255,
}
flavor_memory = {
'type': ['string', 'null'],
'minLength': 2,
'maxLength': 255
}
flavor_additional_details = {
'type': ['object', 'null'],
'minLength': 2,
'maxLength': 255
}
flavor_python_version = {
'type': ['string', 'null', 'number', 'integer'],
'minLength': 2,
'maxLength': 255
}
hex_uuid = {
'type': 'string',
'maxLength': 32,

View File

@ -0,0 +1,46 @@
#
# 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 itertools
from gyan.api.controllers import link
_basic_keys = (
'id',
'name',
'cpu',
'memory',
'disk',
'driver',
'additional_details'
)
def format_flavor(url, flavor):
def transform(key, value):
if key not in _basic_keys:
return
if key == 'id':
yield ('id', value)
yield ('links', [link.make_link(
'self', url, 'flavors', value),
link.make_link(
'bookmark', url,
'flavors', value,
bookmark=True)])
else:
yield (key, value)
return dict(itertools.chain.from_iterable(
transform(k, v) for k, v in flavor.as_dict().items()))

View File

@ -14,11 +14,13 @@ import itertools
from gyan.common.policies import host
from gyan.common.policies import base
from gyan.common.policies import flavor
from gyan.common.policies import ml_model
def list_rules():
return itertools.chain(
base.list_rules(),
host.list_rules(),
ml_model.list_rules()
ml_model.list_rules(),
flavor.list_rules()
)

View File

@ -0,0 +1,112 @@
# 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 oslo_policy import policy
from gyan.common.policies import base
FLAVOR = 'flavor:%s'
rules = [
policy.DocumentedRuleDefault(
name=FLAVOR % 'create',
check_str=base.RULE_ADMIN_OR_OWNER,
description='Create a new Flavor.',
operations=[
{
'path': '/v1/flavors',
'method': 'POST'
}
]
),
policy.DocumentedRuleDefault(
name=FLAVOR % 'delete',
check_str=base.RULE_ADMIN_OR_OWNER,
description='Delete a Flavor.',
operations=[
{
'path': '/v1/flavors/{flavor_ident}',
'method': 'DELETE'
}
]
),
policy.DocumentedRuleDefault(
name=FLAVOR % 'delete_all_projects',
check_str=base.RULE_ADMIN_API,
description='Delete a flavors from all projects.',
operations=[
{
'path': '/v1/flavors/{flavor_ident}',
'method': 'DELETE'
}
]
),
policy.DocumentedRuleDefault(
name=FLAVOR % 'delete_force',
check_str=base.RULE_ADMIN_API,
description='Forcibly delete a Flavor.',
operations=[
{
'path': '/v1/flavors/{flavor_ident}',
'method': 'DELETE'
}
]
),
policy.DocumentedRuleDefault(
name=FLAVOR % 'get_one',
check_str=base.RULE_ADMIN_OR_OWNER,
description='Retrieve the details of a specific ml model.',
operations=[
{
'path': '/v1/flavors/{flavor_ident}',
'method': 'GET'
}
]
),
policy.DocumentedRuleDefault(
name=FLAVOR % 'get_all',
check_str=base.RULE_ADMIN_OR_OWNER,
description='Retrieve the details of all ml models.',
operations=[
{
'path': '/v1/flavors',
'method': 'GET'
}
]
),
policy.DocumentedRuleDefault(
name=FLAVOR % 'get_all_all_projects',
check_str=base.RULE_ADMIN_API,
description='Retrieve the details of all ml models across projects.',
operations=[
{
'path': '/v1/flavors',
'method': 'GET'
}
]
),
policy.DocumentedRuleDefault(
name=FLAVOR % 'update',
check_str=base.RULE_ADMIN_OR_OWNER,
description='Update a ML Model.',
operations=[
{
'path': '/v1/flavors/{flavor_ident}',
'method': 'PATCH'
}
]
),
]
def list_rules():
return rules

View File

@ -160,6 +160,14 @@ def get_ml_model(ml_model_ident):
return ml_model
def get_flavor(flavor_ident):
flavor = api_utils.get_resource('Flavor', flavor_ident)
if not flavor:
pecan.abort(404, ('Not found; the ml model you requested '
'does not exist.'))
return flavor
def validate_ml_model_state(ml_model, action):
if ml_model.status not in VALID_STATES[action]:
raise exception.InvalidStateException(

View File

@ -115,6 +115,88 @@ def update_ml_model(context, ml_model_id, values):
context, ml_model_id, values)
@profiler.trace("db")
def list_flavors(context, filters=None, limit=None, marker=None,
sort_key=None, sort_dir=None):
"""List matching Flavors.
Return a list of the specified columns for all flavors that match
the specified filters.
:param context: The security context
:param filters: Filters to apply. Defaults to None.
:param limit: Maximum number of flavors to return.
:param marker: the last item of the previous page; we return the next
result set.
:param sort_key: Attribute by which results should be sorted.
:param sort_dir: Direction in which results should be sorted.
(asc, desc)
:returns: A list of tuples of the specified columns.
"""
return _get_dbdriver_instance().list_flavors(
context, filters, limit, marker, sort_key, sort_dir)
@profiler.trace("db")
def create_flavor(context, values):
"""Create a new Flavor.
:param context: The security context
:param values: A dict containing several items used to identify
and track the ML Model
:returns: A ML Model.
"""
return _get_dbdriver_instance().create_flavor(context, values)
@profiler.trace("db")
def get_flavor_by_uuid(context, flavor_uuid):
"""Return a Flavor.
:param context: The security context
:param flavor_uuid: The uuid of a flavor.
:returns: A Flavor.
"""
return _get_dbdriver_instance().get_flavor_by_uuid(
context, flavor_uuid)
@profiler.trace("db")
def get_flavor_by_name(context, flavor_name):
"""Return a Flavor.
:param context: The security context
:param flavor_name: The name of a Flavor.
:returns: A Flavor.
"""
return _get_dbdriver_instance().get_flavor_by_name(
context, flavor_name)
@profiler.trace("db")
def destroy_flavor(context, flavor_id):
"""Destroy a flavor and all associated interfaces.
:param context: Request context
:param flavor_id: The id or uuid of a flavor.
"""
return _get_dbdriver_instance().destroy_flavor(context, flavor_id)
@profiler.trace("db")
def update_flavor(context, flavor_id, values):
"""Update properties of a flavor.
:param context: Request context
:param flavor_id: The id or uuid of a flavor.
:param values: The properties to be updated
:returns: A Flavor.
:raises: FlavorNotFound
"""
return _get_dbdriver_instance().update_flavor(
context, flavor_id, values)
@profiler.trace("db")
def list_compute_hosts(context, filters=None, limit=None, marker=None,
sort_key=None, sort_dir=None):

View File

@ -0,0 +1,34 @@
"""Add flavor table
Revision ID: 395aff469925
Revises: f3bf9414f399
Create Date: 2018-10-22 07:53:38.240884
"""
# revision identifiers, used by Alembic.
revision = '395aff469925'
down_revision = 'f3bf9414f399'
branch_labels = None
depends_on = None
from alembic import op
import sqlalchemy as sa
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('flavor',
sa.Column('created_at', sa.DateTime(), nullable=True),
sa.Column('updated_at', sa.DateTime(), nullable=True),
sa.Column('id', sa.String(length=36), nullable=False),
sa.Column('name', sa.String(length=255), nullable=False),
sa.Column('python_version', sa.String(length=255), nullable=False),
sa.Column('cpu', sa.String(length=255), nullable=False),
sa.Column('driver', sa.String(length=255), nullable=False),
sa.Column('memory', sa.String(length=255), nullable=False),
sa.Column('disk', sa.String(length=255), nullable=False),
sa.Column('additional_details', sa.Text(), nullable=False),
sa.PrimaryKeyConstraint('id')
)
# ### end Alembic commands ###

View File

@ -305,3 +305,80 @@ class Connection(object):
filter_names = ['uuid', 'project_id', 'user_id']
return self._add_filters(query, models.ML_Model, filters=filters,
filter_names=filter_names)
def list_flavors(self, context, filters=None, limit=None,
marker=None, sort_key=None, sort_dir=None):
query = model_query(models.Flavor)
query = self._add_flavors_filters(query, filters)
LOG.debug(filters)
return _paginate_query(models.Flavor, limit, marker,
sort_key, sort_dir, query)
def create_flavor(self, context, values):
# ensure defaults are present for new flavors
if not values.get('id'):
values['id'] = uuidutils.generate_uuid()
flavor = models.Flavor()
flavor.update(values)
try:
flavor.save()
except db_exc.DBDuplicateEntry:
raise exception.FlavorAlreadyExists(field='UUID',
value=values['uuid'])
return flavor
def get_flavor_by_uuid(self, context, flavor_uuid):
query = model_query(models.Flavor)
query = self._add_project_filters(context, query)
query = query.filter_by(id=flavor_uuid)
try:
return query.one()
except NoResultFound:
raise exception.FlavorNotFound(flavor=flavor_uuid)
def get_flavor_by_name(self, context, flavor_name):
query = model_query(models.Flavor)
query = self._add_project_filters(context, query)
query = query.filter_by(name=flavor_name)
try:
return query.one()
except NoResultFound:
raise exception.FlavorNotFound(flavor=flavor_name)
except MultipleResultsFound:
raise exception.Conflict('Multiple flavors exist with same '
'name. Please use the flavor uuid '
'instead.')
def destroy_flavor(self, context, flavor_id):
session = get_session()
with session.begin():
query = model_query(models.Flavor, session=session)
query = add_identity_filter(query, flavor_id)
count = query.delete()
if count != 1:
raise exception.FlavorNotFound(flavor_id)
def update_flavor(self, context, flavor_id, values):
if 'id' in values:
msg = _("Cannot overwrite UUID for an existing ML Model.")
raise exception.InvalidParameterValue(err=msg)
return self._do_update_flavor_id(flavor_id, values)
def _do_update_flavor_id(self, flavor_id, values):
session = get_session()
with session.begin():
query = model_query(models.Flavor, session=session)
query = add_identity_filter(query, flavor_id)
try:
ref = query.with_lockmode('update').one()
except NoResultFound:
raise exception.FlavorNotFound(flavor=flavor_id)
ref.update(values)
return ref
def _add_flavors_filters(self, query, filters):
filter_names = ['id']
return self._add_filters(query, models.Flavor, filters=filters,
filter_names=filter_names)

View File

@ -141,3 +141,20 @@ class ComputeHost(Base):
hostname = Column(String(255), nullable=False)
status = Column(String(255), nullable=False)
type = Column(String(255), nullable=False)
class Flavor(Base):
"""Represents a Flavor. """
__tablename__ = 'flavor'
__table_args__ = (
table_args()
)
id = Column(String(36), primary_key=True, nullable=False)
name = Column(String(255), nullable=False)
python_version = Column(String(255), nullable=False)
cpu = Column(String(255), nullable=False)
driver = Column(String(255), nullable=False)
memory = Column(String(255), nullable=False)
disk = Column(String(255), nullable=False)
additional_details = Column(Text, nullable=False)

View File

@ -11,13 +11,16 @@
# under the License.
from gyan.objects import compute_host
from gyan.objects import flavor
from gyan.objects import ml_model
ComputeHost = compute_host.ComputeHost
Flavor = flavor.Flavor
ML_Model = ml_model.ML_Model
__all__ = (
'ComputeHost',
'ML_Model'
'ML_Model',
'Flavor'
)

161
gyan/objects/flavor.py Normal file
View File

@ -0,0 +1,161 @@
# 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 oslo_log import log as logging
from oslo_versionedobjects import fields
from gyan.common import exception
from gyan.common.i18n import _
from gyan.db import api as dbapi
from gyan.objects import base
from gyan.objects import fields as z_fields
LOG = logging.getLogger(__name__)
@base.GyanObjectRegistry.register
class Flavor(base.GyanPersistentObject, base.GyanObject):
VERSION = '1'
fields = {
'id': fields.UUIDField(nullable=True),
'name': fields.StringField(nullable=True),
'cpu': fields.StringField(nullable=True),
'memory': fields.StringField(nullable=True),
'python_version': fields.StringField(nullable=True),
'disk': fields.BooleanField(nullable=True),
'additional_details': fields.StringField(nullable=True),
'created_at': fields.DateTimeField(tzinfo_aware=False, nullable=True),
'updated_at': fields.DateTimeField(tzinfo_aware=False, nullable=True),
'driver': z_fields.ModelField(nullable=True)
}
@staticmethod
def _from_db_object(flavor, db_flavor):
"""Converts a database entity to a formal object."""
for field in flavor.fields:
setattr(flavor, field, db_flavor[field])
flavor.obj_reset_changes()
return flavor
@staticmethod
def _from_db_object_list(db_objects, cls, context):
"""Converts a list of database entities to a list of formal objects."""
return [Flavor._from_db_object(cls(context), obj)
for obj in db_objects]
@base.remotable_classmethod
def get_by_uuid(cls, context, uuid):
"""Find a ml model based on uuid and return a :class:`ML_Model` object.
:param uuid: the uuid of a ml model.
:param context: Security context
:returns: a :class:`ML_Model` object.
"""
db_flavor = dbapi.get_flavor_by_uuid(context, uuid)
flavor = Flavor._from_db_object(cls(context), db_flavor)
return flavor
@base.remotable_classmethod
def get_by_name(cls, context, name):
"""Find a flavor based on name and return a Flavor object.
:param name: the logical name of a ml model.
:param context: Security context
:returns: a :class:`ML_Model` object.
"""
db_flavor = dbapi.get_flavor_by_name(context, name)
flavor = Flavor._from_db_object(cls(context), db_flavor)
return flavor
@base.remotable_classmethod
def list(cls, context, limit=None, marker=None,
sort_key=None, sort_dir=None, filters=None):
"""Return a list of Flavor objects.
:param context: Security context.
:param limit: maximum number of resources to return in a single result.
:param marker: pagination marker for large data sets.
:param sort_key: column to sort results by.
:param sort_dir: direction to sort. "asc" or "desc".
:param filters: filters when list ml models, the filter name could be
'name', 'project_id', 'user_id'.
:returns: a list of :class:`ML_Model` object.
"""
db_flavors = dbapi.list_flavors(
context, limit=limit, marker=marker, sort_key=sort_key,
sort_dir=sort_dir, filters=filters)
return Flavor._from_db_object_list(db_flavors, cls, context)
def create(self, context):
"""Create a Flavor record in the DB.
:param context: Security context. NOTE: This should only
be used internally by the indirection_api.
Unfortunately, RPC requires context as the first
argument, even though we don't use it.
A context should be set when instantiating the
object, e.g.: ML_Model(context)
"""
values = self.obj_get_changes()
db_flavor = dbapi.create_flavor(context, values)
return self._from_db_object(self, db_flavor)
@base.remotable
def destroy(self, context=None):
"""Delete the Flavor from the DB.
:param context: Security context. NOTE: This should only
be used internally by the indirection_api.
Unfortunately, RPC requires context as the first
argument, even though we don't use it.
A context should be set when instantiating the
object, e.g.: ML Model(context)
"""
dbapi.destroy_flavor(context, self.id)
self.obj_reset_changes()
@base.remotable
def save(self, context=None):
"""Save updates to this Flavor.
Updates will be made column by column based on the result
of self.what_changed().
:param context: Security context. NOTE: This should only
be used internally by the indirection_api.
Unfortunately, RPC requires context as the first
argument, even though we don't use it.
A context should be set when instantiating the
object, e.g.: ML Model(context)
"""
updates = self.obj_get_changes()
dbapi.update_ml_model(context, self.id, updates)
self.obj_reset_changes()
def obj_load_attr(self, attrname):
if not self._context:
raise exception.OrphanedObjectError(method='obj_load_attr',
objtype=self.obj_name())
LOG.debug("Lazy-loading '%(attr)s' on %(name)s uuid %(uuid)s",
{'attr': attrname,
'name': self.obj_name(),
'uuid': self.uuid,
})
self.obj_reset_changes([attrname])