Add Flavor API
Change-Id: I26a0671d3a7431c52de898e3ea91c890dc3eb287
This commit is contained in:
parent
26e2299908
commit
06e72f347b
@ -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)
|
||||
|
||||
|
||||
|
210
gyan/api/controllers/v1/flavors.py
Normal file
210
gyan/api/controllers/v1/flavors.py
Normal 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
|
58
gyan/api/controllers/v1/schemas/flavors.py
Normal file
58
gyan/api/controllers/v1/schemas/flavors.py
Normal 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
|
||||
}
|
@ -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,
|
||||
|
46
gyan/api/controllers/v1/views/flavors_view.py
Normal file
46
gyan/api/controllers/v1/views/flavors_view.py
Normal 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()))
|
@ -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()
|
||||
)
|
||||
|
112
gyan/common/policies/flavor.py
Normal file
112
gyan/common/policies/flavor.py
Normal 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
|
@ -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(
|
||||
|
@ -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):
|
||||
|
@ -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 ###
|
@ -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)
|
@ -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)
|
||||
|
@ -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
161
gyan/objects/flavor.py
Normal 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])
|
Loading…
Reference in New Issue
Block a user