Browse Source

Add Flavor API

Change-Id: I26a0671d3a7431c52de898e3ea91c890dc3eb287
bharath 6 months ago
parent
commit
06e72f347b

+ 11
- 2
gyan/api/controllers/v1/__init__.py View File

@@ -22,6 +22,7 @@ import pecan
22 22
 from gyan.api.controllers import base as controllers_base
23 23
 from gyan.api.controllers import link
24 24
 from gyan.api.controllers.v1 import hosts as host_controller
25
+from gyan.api.controllers.v1 import flavors as flavor_controller
25 26
 from gyan.api.controllers.v1 import ml_models as ml_model_controller
26 27
 from gyan.api.controllers import versions as ver
27 28
 from gyan.api import http_error
@@ -59,7 +60,8 @@ class V1(controllers_base.APIBase):
59 60
         'media_types',
60 61
         'links',
61 62
         'hosts',
62
-        'ml_models'
63
+        'ml_models',
64
+        'flavors'
63 65
     )
64 66
 
65 67
     @staticmethod
@@ -81,6 +83,12 @@ class V1(controllers_base.APIBase):
81 83
                                         pecan.request.host_url,
82 84
                                         'hosts', '',
83 85
                                         bookmark=True)]
86
+        v1.flavors = [link.make_link('self', pecan.request.host_url,
87
+                                   'flavors', ''),
88
+                    link.make_link('bookmark',
89
+                                   pecan.request.host_url,
90
+                                   'flavors', '',
91
+                                   bookmark=True)]
84 92
         v1.ml_models = [link.make_link('self', pecan.request.host_url,
85 93
                                     'ml-models', ''),
86 94
                      link.make_link('bookmark',
@@ -95,6 +103,7 @@ class Controller(controllers_base.Controller):
95 103
 
96 104
     hosts = host_controller.HostController()
97 105
     ml_models = ml_model_controller.MLModelController()
106
+    flavors = flavor_controller.FlavorController()
98 107
 
99 108
     @pecan.expose('json')
100 109
     def get(self):
@@ -148,7 +157,7 @@ class Controller(controllers_base.Controller):
148 157
                     'method': pecan.request.method,
149 158
                     'body': pecan.request.body})
150 159
             # LOG.debug(msg)
151
-        LOG.debug(args)
160
+        # LOG.debug(args)
152 161
         return super(Controller, self)._route(args)
153 162
 
154 163
 

+ 210
- 0
gyan/api/controllers/v1/flavors.py View File

@@ -0,0 +1,210 @@
1
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
2
+#    not use this file except in compliance with the License. You may obtain
3
+#    a copy of the License at
4
+#
5
+#         http://www.apache.org/licenses/LICENSE-2.0
6
+#
7
+#    Unless required by applicable law or agreed to in writing, software
8
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
9
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
10
+#    License for the specific language governing permissions and limitations
11
+#    under the License.
12
+
13
+import base64
14
+import shlex
15
+import json
16
+
17
+from oslo_log import log as logging
18
+from oslo_utils import strutils
19
+from oslo_utils import uuidutils
20
+import pecan
21
+import six
22
+
23
+from gyan.api.controllers import base
24
+from gyan.api.controllers import link
25
+from gyan.api.controllers.v1 import collection
26
+from gyan.api.controllers.v1.schemas import flavors as schema
27
+from gyan.api.controllers.v1.views import flavors_view as view
28
+from gyan.api import utils as api_utils
29
+from gyan.api import validation
30
+from gyan.common import consts
31
+from gyan.common import context as gyan_context
32
+from gyan.common import exception
33
+from gyan.common.i18n import _
34
+from gyan.common.policies import flavor as policies
35
+from gyan.common import policy
36
+from gyan.common import utils
37
+import gyan.conf
38
+from gyan import objects
39
+
40
+CONF = gyan.conf.CONF
41
+LOG = logging.getLogger(__name__)
42
+
43
+
44
+def check_policy_on_flavor(flavor, action):
45
+    context = pecan.request.context
46
+    policy.enforce(context, action, flavor, action=action)
47
+
48
+
49
+class FlavorCollection(collection.Collection):
50
+    """API representation of a collection of flavors."""
51
+
52
+    fields = {
53
+        'flavors',
54
+        'next'
55
+    }
56
+
57
+    """A list containing flavor objects"""
58
+
59
+    def __init__(self, **kwargs):
60
+        super(FlavorCollection, self).__init__(**kwargs)
61
+        self._type = 'flavors'
62
+
63
+    @staticmethod
64
+    def convert_with_links(rpc_flavors, limit, url=None,
65
+                           expand=False, **kwargs):
66
+        context = pecan.request.context
67
+        collection = FlavorCollection()
68
+        collection.flavors = \
69
+            [view.format_flavor(url, p)
70
+             for p in rpc_flavors]
71
+        collection.next = collection.get_next(limit, url=url, **kwargs)
72
+        return collection
73
+
74
+
75
+class FlavorController(base.Controller):
76
+    """Controller for Flavors."""
77
+
78
+    @pecan.expose('json')
79
+    @exception.wrap_pecan_controller_exception
80
+    def get_all(self, **kwargs):
81
+        """Retrieve a list of flavors.
82
+
83
+        """
84
+        context = pecan.request.context
85
+        policy.enforce(context, "flavor:get_all",
86
+                       action="flavor:get_all")
87
+        return self._get_flavors_collection(**kwargs)
88
+
89
+    def _get_flavors_collection(self, **kwargs):
90
+        context = pecan.request.context
91
+        if utils.is_all_projects(kwargs):
92
+            policy.enforce(context, "flavor:get_all_all_projects",
93
+                           action="flavor:get_all_all_projects")
94
+            context.all_projects = True
95
+        kwargs.pop('all_projects', None)
96
+        limit = api_utils.validate_limit(kwargs.pop('limit', None))
97
+        sort_dir = api_utils.validate_sort_dir(kwargs.pop('sort_dir', 'asc'))
98
+        sort_key = kwargs.pop('sort_key', 'id')
99
+        resource_url = kwargs.pop('resource_url', None)
100
+        expand = kwargs.pop('expand', None)
101
+
102
+        flavor_allowed_filters = ['name', 'cpu', 'python_version', 'driver',
103
+                                   'memory', 'disk', 'additional_details']
104
+        filters = {}
105
+        for filter_key in flavor_allowed_filters:
106
+            if filter_key in kwargs:
107
+                policy_action = policies.FLAVOR % ('get_one:' + filter_key)
108
+                context.can(policy_action, might_not_exist=True)
109
+                filter_value = kwargs.pop(filter_key)
110
+                filters[filter_key] = filter_value
111
+        marker_obj = None
112
+        marker = kwargs.pop('marker', None)
113
+        if marker:
114
+            marker_obj = objects.Flavor.get_by_uuid(context,
115
+                                                      marker)
116
+        if kwargs:
117
+            unknown_params = [str(k) for k in kwargs]
118
+            msg = _("Unknown parameters: %s") % ", ".join(unknown_params)
119
+            raise exception.InvalidValue(msg)
120
+
121
+        flavors = objects.Flavor.list(context,
122
+                                          limit,
123
+                                          marker_obj,
124
+                                          sort_key,
125
+                                          sort_dir,
126
+                                          filters=filters)
127
+        return FlavorCollection.convert_with_links(flavors, limit,
128
+                                                    url=resource_url,
129
+                                                    expand=expand,
130
+                                                    sort_key=sort_key,
131
+                                                    sort_dir=sort_dir)
132
+
133
+    @pecan.expose('json')
134
+    @exception.wrap_pecan_controller_exception
135
+    def get_one(self, flavor_ident, **kwargs):
136
+        """Retrieve information about the given flavor.
137
+
138
+        :param flavor_ident: UUID or name of a flavor.
139
+        """
140
+        context = pecan.request.context
141
+        if utils.is_all_projects(kwargs):
142
+            policy.enforce(context, "flavor:get_one_all_projects",
143
+                           action="flavor:get_one_all_projects")
144
+            context.all_projects = True
145
+        flavor = utils.get_flavor(flavor_ident)
146
+        check_policy_on_flavor(flavor.as_dict(), "flavor:get_one")
147
+        return view.format_flavor(pecan.request.host_url,
148
+                                    flavor)
149
+
150
+    @base.Controller.api_version("1.0")
151
+    @pecan.expose('json')
152
+    @api_utils.enforce_content_types(['application/json'])
153
+    @exception.wrap_pecan_controller_exception
154
+    @validation.validated(schema.flavor_create)
155
+    def post(self, **flavor_dict):
156
+        return self._do_post(**flavor_dict)
157
+
158
+    def _do_post(self, **flavor_dict):
159
+        """Create or run a new flavor.
160
+
161
+        :param flavor_dict: a flavor within the request body.
162
+        """
163
+        context = pecan.request.context
164
+        policy.enforce(context, "flavor:create",
165
+                       action="flavor:create")
166
+
167
+        LOG.debug("bhaaaaaaaaaaaaaaaaaaaaaaaaaaa")
168
+        LOG.debug(flavor_dict)
169
+        flavor_dict["additional_details"] = json.dumps(flavor_dict["additional_details"])
170
+        LOG.debug(flavor_dict)
171
+        # flavor_dict["model_data"] = open("/home/bharath/model.zip", "rb").read()
172
+        new_flavor = objects.Flavor(context, **flavor_dict)
173
+        flavor = new_flavor.create(context)
174
+        LOG.debug(new_flavor)
175
+        # compute_api.flavor_create(context, new_flavor)
176
+        # Set the HTTP Location Header
177
+        pecan.response.location = link.build_url('flavors',
178
+                                                 flavor.id)
179
+        pecan.response.status = 201
180
+        return view.format_flavor(pecan.request.host_url,
181
+                                    flavor)
182
+
183
+    @pecan.expose('json')
184
+    @exception.wrap_pecan_controller_exception
185
+    def patch(self, flavor_ident, **patch):
186
+        """Update an existing flavor.
187
+
188
+        :param flavor_ident: UUID or name of a flavor.
189
+        :param patch: a json PATCH document to apply to this flavor.
190
+        """
191
+        context = pecan.request.context
192
+        flavor = utils.get_flavor(flavor_ident)
193
+        check_policy_on_flavor(flavor.as_dict(), "flavor:update")
194
+        return view.format_flavor(context, pecan.request.host_url,
195
+                                    flavor.as_dict())
196
+
197
+    @pecan.expose('json')
198
+    @exception.wrap_pecan_controller_exception
199
+    @validation.validate_query_param(pecan.request, schema.query_param_delete)
200
+    def delete(self, flavor_ident, **kwargs):
201
+        """Delete a flavor.
202
+
203
+        :param flavor_ident: UUID or Name of a Flavor.
204
+        :param force: If True, allow to force delete the Flavor.
205
+        """
206
+        context = pecan.request.context
207
+        flavor = utils.get_flavor(flavor_ident)
208
+        check_policy_on_flavor(flavor.as_dict(), "flavor:delete")
209
+        flavor.destroy(context)
210
+        pecan.response.status = 204

+ 58
- 0
gyan/api/controllers/v1/schemas/flavors.py View File

@@ -0,0 +1,58 @@
1
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
2
+# not use this file except in compliance with the License. You may obtain
3
+# a copy of the License at
4
+#
5
+# http://www.apache.org/licenses/LICENSE-2.0
6
+#
7
+# Unless required by applicable law or agreed to in writing, software
8
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
9
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
10
+# License for the specific language governing permissions and limitations
11
+# under the License.
12
+
13
+import copy
14
+
15
+from gyan.api.controllers.v1.schemas import parameter_types
16
+
17
+_flavor_properties = {}
18
+
19
+flavor_create = {
20
+    'type': 'object',
21
+    'properties': {
22
+        "name": parameter_types.flavor_name,
23
+        "driver": parameter_types.flavor_driver,
24
+        "cpu": parameter_types.flavor_cpu,
25
+        "disk": parameter_types.flavor_disk,
26
+        'memory': parameter_types.flavor_memory,
27
+        'python_version': parameter_types.flavor_python_version,
28
+        'additional_details': parameter_types.flavor_additional_details
29
+
30
+    },
31
+    'required': ['name', 'cpu', 'memory', 'python_version', 'disk', 'driver', 'additional_details'],
32
+    'additionalProperties': False
33
+}
34
+
35
+
36
+query_param_create = {
37
+    'type': 'object',
38
+    'properties': {
39
+        'run': parameter_types.boolean_extended
40
+    },
41
+    'additionalProperties': False
42
+}
43
+
44
+ml_model_update = {
45
+    'type': 'object',
46
+    'properties': {},
47
+    'additionalProperties': False
48
+}
49
+
50
+query_param_delete = {
51
+    'type': 'object',
52
+    'properties': {
53
+        'force': parameter_types.boolean_extended,
54
+        'all_projects': parameter_types.boolean_extended,
55
+        'stop': parameter_types.boolean_extended
56
+    },
57
+    'additionalProperties': False
58
+}

+ 44
- 0
gyan/api/controllers/v1/schemas/parameter_types.py View File

@@ -47,6 +47,50 @@ ml_model_name = {
47 47
     'pattern': '^[a-zA-Z0-9][a-zA-Z0-9_.-]+$'
48 48
 }
49 49
 
50
+flavor_name = {
51
+    'type': ['string', 'null'],
52
+    'minLength': 2,
53
+    'maxLength': 255,
54
+    'pattern': '^[a-zA-Z0-9][a-zA-Z0-9_.-]+$'
55
+}
56
+
57
+flavor_cpu = {
58
+    'type': ['number', 'integer', 'null'],
59
+    'minLength': 2,
60
+    'maxLength': 255
61
+}
62
+
63
+flavor_driver = {
64
+    'type': ['string', 'null'],
65
+    'minLength': 2,
66
+    'maxLength': 255,
67
+    'pattern': '^[a-zA-Z0-9][a-zA-Z0-9_.-]+$'
68
+}
69
+
70
+flavor_disk = {
71
+    'type': ['string', 'null'],
72
+    'minLength': 2,
73
+    'maxLength': 255,
74
+}
75
+
76
+flavor_memory = {
77
+    'type': ['string', 'null'],
78
+    'minLength': 2,
79
+    'maxLength': 255
80
+}
81
+
82
+flavor_additional_details = {
83
+    'type': ['object', 'null'],
84
+    'minLength': 2,
85
+    'maxLength': 255
86
+}
87
+
88
+flavor_python_version = {
89
+    'type': ['string', 'null', 'number', 'integer'],
90
+    'minLength': 2,
91
+    'maxLength': 255
92
+}
93
+
50 94
 hex_uuid = {
51 95
     'type': 'string',
52 96
     'maxLength': 32,

+ 46
- 0
gyan/api/controllers/v1/views/flavors_view.py View File

@@ -0,0 +1,46 @@
1
+#
2
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
3
+#    not use this file except in compliance with the License. You may obtain
4
+#    a copy of the License at
5
+#
6
+#         http://www.apache.org/licenses/LICENSE-2.0
7
+#
8
+#    Unless required by applicable law or agreed to in writing, software
9
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
10
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
11
+#    License for the specific language governing permissions and limitations
12
+#    under the License.
13
+
14
+import itertools
15
+
16
+from gyan.api.controllers import link
17
+
18
+
19
+_basic_keys = (
20
+    'id',
21
+    'name',
22
+    'cpu',
23
+    'memory',
24
+    'disk',
25
+    'driver',
26
+    'additional_details'
27
+)
28
+
29
+
30
+def format_flavor(url, flavor):
31
+    def transform(key, value):
32
+        if key not in _basic_keys:
33
+            return
34
+        if key == 'id':
35
+            yield ('id', value)
36
+            yield ('links', [link.make_link(
37
+                'self', url, 'flavors', value),
38
+                link.make_link(
39
+                    'bookmark', url,
40
+                    'flavors', value,
41
+                    bookmark=True)])
42
+        else:
43
+            yield (key, value)
44
+
45
+    return dict(itertools.chain.from_iterable(
46
+        transform(k, v) for k, v in flavor.as_dict().items()))

+ 3
- 1
gyan/common/policies/__init__.py View File

@@ -14,11 +14,13 @@ import itertools
14 14
 
15 15
 from gyan.common.policies import host
16 16
 from gyan.common.policies import base
17
+from gyan.common.policies import flavor
17 18
 from gyan.common.policies import ml_model
18 19
 
19 20
 def list_rules():
20 21
     return itertools.chain(
21 22
         base.list_rules(),
22 23
         host.list_rules(),
23
-        ml_model.list_rules()
24
+        ml_model.list_rules(),
25
+        flavor.list_rules()
24 26
     )

+ 112
- 0
gyan/common/policies/flavor.py View File

@@ -0,0 +1,112 @@
1
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
2
+# not use this file except in compliance with the License. You may obtain
3
+# a copy of the License at
4
+#
5
+#      http://www.apache.org/licenses/LICENSE-2.0
6
+#
7
+# Unless required by applicable law or agreed to in writing, software
8
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
9
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
10
+# License for the specific language governing permissions and limitations
11
+# under the License.
12
+
13
+from oslo_policy import policy
14
+
15
+from gyan.common.policies import base
16
+
17
+FLAVOR = 'flavor:%s'
18
+
19
+rules = [
20
+    policy.DocumentedRuleDefault(
21
+        name=FLAVOR % 'create',
22
+        check_str=base.RULE_ADMIN_OR_OWNER,
23
+        description='Create a new Flavor.',
24
+        operations=[
25
+            {
26
+                'path': '/v1/flavors',
27
+                'method': 'POST'
28
+            }
29
+        ]
30
+    ),
31
+    policy.DocumentedRuleDefault(
32
+        name=FLAVOR % 'delete',
33
+        check_str=base.RULE_ADMIN_OR_OWNER,
34
+        description='Delete a Flavor.',
35
+        operations=[
36
+            {
37
+                'path': '/v1/flavors/{flavor_ident}',
38
+                'method': 'DELETE'
39
+            }
40
+        ]
41
+    ),
42
+    policy.DocumentedRuleDefault(
43
+        name=FLAVOR % 'delete_all_projects',
44
+        check_str=base.RULE_ADMIN_API,
45
+        description='Delete a flavors from all projects.',
46
+        operations=[
47
+            {
48
+                'path': '/v1/flavors/{flavor_ident}',
49
+                'method': 'DELETE'
50
+            }
51
+        ]
52
+    ),
53
+    policy.DocumentedRuleDefault(
54
+        name=FLAVOR % 'delete_force',
55
+        check_str=base.RULE_ADMIN_API,
56
+        description='Forcibly delete a Flavor.',
57
+        operations=[
58
+            {
59
+                'path': '/v1/flavors/{flavor_ident}',
60
+                'method': 'DELETE'
61
+            }
62
+        ]
63
+    ),
64
+    policy.DocumentedRuleDefault(
65
+        name=FLAVOR % 'get_one',
66
+        check_str=base.RULE_ADMIN_OR_OWNER,
67
+        description='Retrieve the details of a specific ml model.',
68
+        operations=[
69
+            {
70
+                'path': '/v1/flavors/{flavor_ident}',
71
+                'method': 'GET'
72
+            }
73
+        ]
74
+    ),
75
+    policy.DocumentedRuleDefault(
76
+        name=FLAVOR % 'get_all',
77
+        check_str=base.RULE_ADMIN_OR_OWNER,
78
+        description='Retrieve the details of all ml models.',
79
+        operations=[
80
+            {
81
+                'path': '/v1/flavors',
82
+                'method': 'GET'
83
+            }
84
+        ]
85
+    ),
86
+    policy.DocumentedRuleDefault(
87
+        name=FLAVOR % 'get_all_all_projects',
88
+        check_str=base.RULE_ADMIN_API,
89
+        description='Retrieve the details of all ml models across projects.',
90
+        operations=[
91
+            {
92
+                'path': '/v1/flavors',
93
+                'method': 'GET'
94
+            }
95
+        ]
96
+    ),
97
+    policy.DocumentedRuleDefault(
98
+        name=FLAVOR % 'update',
99
+        check_str=base.RULE_ADMIN_OR_OWNER,
100
+        description='Update a ML Model.',
101
+        operations=[
102
+            {
103
+                'path': '/v1/flavors/{flavor_ident}',
104
+                'method': 'PATCH'
105
+            }
106
+        ]
107
+    ),
108
+]
109
+
110
+
111
+def list_rules():
112
+    return rules

+ 8
- 0
gyan/common/utils.py View File

@@ -160,6 +160,14 @@ def get_ml_model(ml_model_ident):
160 160
 
161 161
     return ml_model
162 162
 
163
+def get_flavor(flavor_ident):
164
+    flavor = api_utils.get_resource('Flavor', flavor_ident)
165
+    if not flavor:
166
+        pecan.abort(404, ('Not found; the ml model you requested '
167
+                          'does not exist.'))
168
+
169
+    return flavor
170
+
163 171
 def validate_ml_model_state(ml_model, action):
164 172
     if ml_model.status not in VALID_STATES[action]:
165 173
         raise exception.InvalidStateException(

+ 82
- 0
gyan/db/api.py View File

@@ -115,6 +115,88 @@ def update_ml_model(context, ml_model_id, values):
115 115
         context, ml_model_id, values)
116 116
 
117 117
 
118
+@profiler.trace("db")
119
+def list_flavors(context, filters=None, limit=None, marker=None,
120
+                    sort_key=None, sort_dir=None):
121
+    """List matching Flavors.
122
+
123
+    Return a list of the specified columns for all flavors that match
124
+    the specified filters.
125
+
126
+    :param context: The security context
127
+    :param filters: Filters to apply. Defaults to None.
128
+    :param limit: Maximum number of flavors to return.
129
+    :param marker: the last item of the previous page; we return the next
130
+                   result set.
131
+    :param sort_key: Attribute by which results should be sorted.
132
+    :param sort_dir: Direction in which results should be sorted.
133
+                     (asc, desc)
134
+    :returns: A list of tuples of the specified columns.
135
+    """
136
+    return _get_dbdriver_instance().list_flavors(
137
+        context, filters, limit, marker, sort_key, sort_dir)
138
+
139
+
140
+@profiler.trace("db")
141
+def create_flavor(context, values):
142
+    """Create a new Flavor.
143
+
144
+    :param context: The security context
145
+    :param values: A dict containing several items used to identify
146
+                   and track the ML Model
147
+    :returns: A ML Model.
148
+    """
149
+    return _get_dbdriver_instance().create_flavor(context, values)
150
+
151
+
152
+@profiler.trace("db")
153
+def get_flavor_by_uuid(context, flavor_uuid):
154
+    """Return a Flavor.
155
+
156
+    :param context: The security context
157
+    :param flavor_uuid: The uuid of a flavor.
158
+    :returns: A Flavor.
159
+    """
160
+    return _get_dbdriver_instance().get_flavor_by_uuid(
161
+        context, flavor_uuid)
162
+
163
+
164
+@profiler.trace("db")
165
+def get_flavor_by_name(context, flavor_name):
166
+    """Return a Flavor.
167
+
168
+    :param context: The security context
169
+    :param flavor_name: The name of a Flavor.
170
+    :returns: A Flavor.
171
+    """
172
+    return _get_dbdriver_instance().get_flavor_by_name(
173
+        context, flavor_name)
174
+
175
+
176
+@profiler.trace("db")
177
+def destroy_flavor(context, flavor_id):
178
+    """Destroy a flavor and all associated interfaces.
179
+
180
+    :param context: Request context
181
+    :param flavor_id: The id or uuid of a flavor.
182
+    """
183
+    return _get_dbdriver_instance().destroy_flavor(context, flavor_id)
184
+
185
+
186
+@profiler.trace("db")
187
+def update_flavor(context, flavor_id, values):
188
+    """Update properties of a flavor.
189
+
190
+    :param context: Request context
191
+    :param flavor_id: The id or uuid of a flavor.
192
+    :param values: The properties to be updated
193
+    :returns: A Flavor.
194
+    :raises: FlavorNotFound
195
+    """
196
+    return _get_dbdriver_instance().update_flavor(
197
+        context, flavor_id, values)
198
+
199
+
118 200
 @profiler.trace("db")
119 201
 def list_compute_hosts(context, filters=None, limit=None, marker=None,
120 202
                        sort_key=None, sort_dir=None):

+ 34
- 0
gyan/db/sqlalchemy/alembic/versions/395aff469925_add_flavor_table.py View File

@@ -0,0 +1,34 @@
1
+"""Add flavor table
2
+
3
+Revision ID: 395aff469925
4
+Revises: f3bf9414f399
5
+Create Date: 2018-10-22 07:53:38.240884
6
+
7
+"""
8
+
9
+# revision identifiers, used by Alembic.
10
+revision = '395aff469925'
11
+down_revision = 'f3bf9414f399'
12
+branch_labels = None
13
+depends_on = None
14
+
15
+from alembic import op
16
+import sqlalchemy as sa
17
+
18
+
19
+def upgrade():
20
+    # ### commands auto generated by Alembic - please adjust! ###
21
+    op.create_table('flavor',
22
+    sa.Column('created_at', sa.DateTime(), nullable=True),
23
+    sa.Column('updated_at', sa.DateTime(), nullable=True),
24
+    sa.Column('id', sa.String(length=36), nullable=False),
25
+    sa.Column('name', sa.String(length=255), nullable=False),
26
+    sa.Column('python_version', sa.String(length=255), nullable=False),
27
+    sa.Column('cpu', sa.String(length=255), nullable=False),
28
+    sa.Column('driver', sa.String(length=255), nullable=False),
29
+    sa.Column('memory', sa.String(length=255), nullable=False),
30
+    sa.Column('disk', sa.String(length=255), nullable=False),
31
+    sa.Column('additional_details', sa.Text(), nullable=False),
32
+    sa.PrimaryKeyConstraint('id')
33
+    )
34
+    # ### end Alembic commands ###

+ 77
- 0
gyan/db/sqlalchemy/api.py View File

@@ -305,3 +305,80 @@ class Connection(object):
305 305
         filter_names = ['uuid', 'project_id', 'user_id']
306 306
         return self._add_filters(query, models.ML_Model, filters=filters,
307 307
                                  filter_names=filter_names)
308
+
309
+    def list_flavors(self, context, filters=None, limit=None,
310
+                      marker=None, sort_key=None, sort_dir=None):
311
+        query = model_query(models.Flavor)
312
+        query = self._add_flavors_filters(query, filters)
313
+        LOG.debug(filters)
314
+        return _paginate_query(models.Flavor, limit, marker,
315
+                               sort_key, sort_dir, query)
316
+
317
+    def create_flavor(self, context, values):
318
+        # ensure defaults are present for new flavors
319
+        if not values.get('id'):
320
+            values['id'] = uuidutils.generate_uuid()
321
+        flavor = models.Flavor()
322
+        flavor.update(values)
323
+        try:
324
+            flavor.save()
325
+        except db_exc.DBDuplicateEntry:
326
+            raise exception.FlavorAlreadyExists(field='UUID',
327
+                                                 value=values['uuid'])
328
+        return flavor
329
+
330
+    def get_flavor_by_uuid(self, context, flavor_uuid):
331
+        query = model_query(models.Flavor)
332
+        query = self._add_project_filters(context, query)
333
+        query = query.filter_by(id=flavor_uuid)
334
+        try:
335
+            return query.one()
336
+        except NoResultFound:
337
+            raise exception.FlavorNotFound(flavor=flavor_uuid)
338
+
339
+    def get_flavor_by_name(self, context, flavor_name):
340
+        query = model_query(models.Flavor)
341
+        query = self._add_project_filters(context, query)
342
+        query = query.filter_by(name=flavor_name)
343
+        try:
344
+            return query.one()
345
+        except NoResultFound:
346
+            raise exception.FlavorNotFound(flavor=flavor_name)
347
+        except MultipleResultsFound:
348
+            raise exception.Conflict('Multiple flavors exist with same '
349
+                                     'name. Please use the flavor uuid '
350
+                                     'instead.')
351
+
352
+    def destroy_flavor(self, context, flavor_id):
353
+        session = get_session()
354
+        with session.begin():
355
+            query = model_query(models.Flavor, session=session)
356
+            query = add_identity_filter(query, flavor_id)
357
+            count = query.delete()
358
+            if count != 1:
359
+                raise exception.FlavorNotFound(flavor_id)
360
+
361
+    def update_flavor(self, context, flavor_id, values):
362
+        if 'id' in values:
363
+            msg = _("Cannot overwrite UUID for an existing ML Model.")
364
+            raise exception.InvalidParameterValue(err=msg)
365
+
366
+        return self._do_update_flavor_id(flavor_id, values)
367
+
368
+    def _do_update_flavor_id(self, flavor_id, values):
369
+        session = get_session()
370
+        with session.begin():
371
+            query = model_query(models.Flavor, session=session)
372
+            query = add_identity_filter(query, flavor_id)
373
+            try:
374
+                ref = query.with_lockmode('update').one()
375
+            except NoResultFound:
376
+                raise exception.FlavorNotFound(flavor=flavor_id)
377
+
378
+            ref.update(values)
379
+        return ref
380
+
381
+    def _add_flavors_filters(self, query, filters):
382
+        filter_names = ['id']
383
+        return self._add_filters(query, models.Flavor, filters=filters,
384
+                                 filter_names=filter_names)

+ 17
- 0
gyan/db/sqlalchemy/models.py View File

@@ -141,3 +141,20 @@ class ComputeHost(Base):
141 141
     hostname = Column(String(255), nullable=False)
142 142
     status = Column(String(255), nullable=False)
143 143
     type = Column(String(255), nullable=False)
144
+
145
+
146
+class Flavor(Base):
147
+    """Represents a Flavor. """
148
+
149
+    __tablename__ = 'flavor'
150
+    __table_args__ = (
151
+        table_args()
152
+    )
153
+    id = Column(String(36), primary_key=True, nullable=False)
154
+    name = Column(String(255), nullable=False)
155
+    python_version = Column(String(255), nullable=False)
156
+    cpu = Column(String(255), nullable=False)
157
+    driver = Column(String(255), nullable=False)
158
+    memory = Column(String(255), nullable=False)
159
+    disk = Column(String(255), nullable=False)
160
+    additional_details = Column(Text, nullable=False)

+ 4
- 1
gyan/objects/__init__.py View File

@@ -11,13 +11,16 @@
11 11
 #    under the License.
12 12
 
13 13
 from gyan.objects import compute_host
14
+from gyan.objects import flavor
14 15
 from gyan.objects import ml_model
15 16
 
16 17
 
17 18
 ComputeHost = compute_host.ComputeHost
19
+Flavor = flavor.Flavor
18 20
 ML_Model = ml_model.ML_Model
19 21
 
20 22
 __all__ = (
21 23
     'ComputeHost',
22
-    'ML_Model'
24
+    'ML_Model',
25
+    'Flavor'
23 26
 )

+ 161
- 0
gyan/objects/flavor.py View File

@@ -0,0 +1,161 @@
1
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
2
+#    not use this file except in compliance with the License. You may obtain
3
+#    a copy of the License at
4
+#
5
+#         http://www.apache.org/licenses/LICENSE-2.0
6
+#
7
+#    Unless required by applicable law or agreed to in writing, software
8
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
9
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
10
+#    License for the specific language governing permissions and limitations
11
+#    under the License.
12
+
13
+from oslo_log import log as logging
14
+from oslo_versionedobjects import fields
15
+
16
+from gyan.common import exception
17
+from gyan.common.i18n import _
18
+from gyan.db import api as dbapi
19
+from gyan.objects import base
20
+from gyan.objects import fields as z_fields
21
+
22
+
23
+LOG = logging.getLogger(__name__)
24
+
25
+
26
+@base.GyanObjectRegistry.register
27
+class Flavor(base.GyanPersistentObject, base.GyanObject):
28
+    VERSION = '1'
29
+
30
+    fields = {
31
+        'id': fields.UUIDField(nullable=True),
32
+        'name': fields.StringField(nullable=True),
33
+        'cpu': fields.StringField(nullable=True),
34
+        'memory': fields.StringField(nullable=True),
35
+        'python_version': fields.StringField(nullable=True),
36
+        'disk': fields.BooleanField(nullable=True),
37
+        'additional_details': fields.StringField(nullable=True),
38
+        'created_at': fields.DateTimeField(tzinfo_aware=False, nullable=True),
39
+        'updated_at': fields.DateTimeField(tzinfo_aware=False, nullable=True),
40
+        'driver': z_fields.ModelField(nullable=True)
41
+    }
42
+
43
+    @staticmethod
44
+    def _from_db_object(flavor, db_flavor):
45
+        """Converts a database entity to a formal object."""
46
+        for field in flavor.fields:
47
+            setattr(flavor, field, db_flavor[field])
48
+
49
+        flavor.obj_reset_changes()
50
+        return flavor
51
+
52
+    @staticmethod
53
+    def _from_db_object_list(db_objects, cls, context):
54
+        """Converts a list of database entities to a list of formal objects."""
55
+        return [Flavor._from_db_object(cls(context), obj)
56
+                for obj in db_objects]
57
+
58
+    @base.remotable_classmethod
59
+    def get_by_uuid(cls, context, uuid):
60
+        """Find a ml model based on uuid and return a :class:`ML_Model` object.
61
+
62
+        :param uuid: the uuid of a ml model.
63
+        :param context: Security context
64
+        :returns: a :class:`ML_Model` object.
65
+        """
66
+        db_flavor = dbapi.get_flavor_by_uuid(context, uuid)
67
+        flavor = Flavor._from_db_object(cls(context), db_flavor)
68
+        return flavor
69
+
70
+    @base.remotable_classmethod
71
+    def get_by_name(cls, context, name):
72
+        """Find a flavor based on name and return a Flavor object.
73
+
74
+        :param name: the logical name of a ml model.
75
+        :param context: Security context
76
+        :returns: a :class:`ML_Model` object.
77
+        """
78
+        db_flavor = dbapi.get_flavor_by_name(context, name)
79
+        flavor = Flavor._from_db_object(cls(context), db_flavor)
80
+        return flavor
81
+
82
+    @base.remotable_classmethod
83
+    def list(cls, context, limit=None, marker=None,
84
+             sort_key=None, sort_dir=None, filters=None):
85
+        """Return a list of Flavor objects.
86
+
87
+        :param context: Security context.
88
+        :param limit: maximum number of resources to return in a single result.
89
+        :param marker: pagination marker for large data sets.
90
+        :param sort_key: column to sort results by.
91
+        :param sort_dir: direction to sort. "asc" or "desc".
92
+        :param filters: filters when list ml models, the filter name could be
93
+                        'name', 'project_id', 'user_id'.
94
+        :returns: a list of :class:`ML_Model` object.
95
+
96
+        """
97
+        db_flavors = dbapi.list_flavors(
98
+            context, limit=limit, marker=marker, sort_key=sort_key,
99
+            sort_dir=sort_dir, filters=filters)
100
+        return Flavor._from_db_object_list(db_flavors, cls, context)
101
+
102
+    def create(self, context):
103
+        """Create a Flavor record in the DB.
104
+
105
+        :param context: Security context. NOTE: This should only
106
+                        be used internally by the indirection_api.
107
+                        Unfortunately, RPC requires context as the first
108
+                        argument, even though we don't use it.
109
+                        A context should be set when instantiating the
110
+                        object, e.g.: ML_Model(context)
111
+
112
+        """
113
+        values = self.obj_get_changes()
114
+        db_flavor = dbapi.create_flavor(context, values)
115
+        return self._from_db_object(self, db_flavor)
116
+
117
+    @base.remotable
118
+    def destroy(self, context=None):
119
+        """Delete the Flavor from the DB.
120
+
121
+        :param context: Security context. NOTE: This should only
122
+                        be used internally by the indirection_api.
123
+                        Unfortunately, RPC requires context as the first
124
+                        argument, even though we don't use it.
125
+                        A context should be set when instantiating the
126
+                        object, e.g.: ML Model(context)
127
+        """
128
+        dbapi.destroy_flavor(context, self.id)
129
+        self.obj_reset_changes()
130
+
131
+    @base.remotable
132
+    def save(self, context=None):
133
+        """Save updates to this Flavor.
134
+
135
+        Updates will be made column by column based on the result
136
+        of self.what_changed().
137
+
138
+        :param context: Security context. NOTE: This should only
139
+                        be used internally by the indirection_api.
140
+                        Unfortunately, RPC requires context as the first
141
+                        argument, even though we don't use it.
142
+                        A context should be set when instantiating the
143
+                        object, e.g.: ML Model(context)
144
+        """
145
+        updates = self.obj_get_changes()
146
+        dbapi.update_ml_model(context, self.id, updates)
147
+
148
+        self.obj_reset_changes()
149
+
150
+    def obj_load_attr(self, attrname):
151
+        if not self._context:
152
+            raise exception.OrphanedObjectError(method='obj_load_attr',
153
+                                                objtype=self.obj_name())
154
+
155
+        LOG.debug("Lazy-loading '%(attr)s' on %(name)s uuid %(uuid)s",
156
+                  {'attr': attrname,
157
+                   'name': self.obj_name(),
158
+                   'uuid': self.uuid,
159
+                   })
160
+
161
+        self.obj_reset_changes([attrname])

Loading…
Cancel
Save