Browse Source

Exposed Cloud Service

Iotronic exposes the services of the boards

Change-Id: Id88c4e6a9d5752d1bffbcfd99827eca0b9b679f7
tags/0.2.0
Fabio Verboso 1 year ago
parent
commit
f450f91c70

+ 100
- 0
iotronic/api/controllers/v1/board.py View File

@@ -152,11 +152,50 @@ class InjectionCollection(collection.Collection):
152 152
         return collection
153 153
 
154 154
 
155
+class ExposedService(base.APIBase):
156
+    service = types.uuid_or_name
157
+    board_uuid = types.uuid_or_name
158
+    public_port = wsme.types.IntegerType()
159
+
160
+    def __init__(self, **kwargs):
161
+        self.fields = []
162
+        fields = list(objects.ExposedService.fields)
163
+        fields.remove('board_uuid')
164
+        for k in fields:
165
+            # Skip fields we do not expose.
166
+            if not hasattr(self, k):
167
+                continue
168
+            self.fields.append(k)
169
+            setattr(self, k, kwargs.get(k, wtypes.Unset))
170
+        setattr(self, 'service', kwargs.get('service_uuid', wtypes.Unset))
171
+
172
+
173
+class ExposedCollection(collection.Collection):
174
+    """API representation of a collection of injection."""
175
+
176
+    exposed = [ExposedService]
177
+
178
+    def __init__(self, **kwargs):
179
+        self._type = 'exposed'
180
+
181
+    @staticmethod
182
+    def get_list(exposed, fields=None):
183
+        collection = ExposedCollection()
184
+        collection.exposed = [ExposedService(**n.as_dict())
185
+                              for n in exposed]
186
+        return collection
187
+
188
+
155 189
 class PluginAction(base.APIBase):
156 190
     action = wsme.wsattr(wtypes.text)
157 191
     parameters = types.jsontype
158 192
 
159 193
 
194
+class ServiceAction(base.APIBase):
195
+    action = wsme.wsattr(wtypes.text)
196
+    parameters = types.jsontype
197
+
198
+
160 199
 class BoardPluginsController(rest.RestController):
161 200
     def __init__(self, board_ident):
162 201
         self.board_ident = board_ident
@@ -282,11 +321,72 @@ class BoardPluginsController(rest.RestController):
282 321
                                                   rpc_board.uuid)
283 322
 
284 323
 
324
+class BoardServicesController(rest.RestController):
325
+    _custom_actions = {
326
+        'action': ['POST'],
327
+    }
328
+
329
+    def __init__(self, board_ident):
330
+        self.board_ident = board_ident
331
+
332
+    def _get_services_on_board_collection(self, board_uuid, fields=None):
333
+        services = objects.ExposedService.list(pecan.request.context,
334
+                                               board_uuid)
335
+
336
+        return ExposedCollection.get_list(services,
337
+                                          fields=fields)
338
+
339
+    @expose.expose(ExposedCollection,
340
+                   status_code=200)
341
+    def get_all(self):
342
+        """Retrieve a list of services of a board.
343
+
344
+        """
345
+        rpc_board = api_utils.get_rpc_board(self.board_ident)
346
+
347
+        cdict = pecan.request.context.to_policy_values()
348
+        cdict['project_id'] = rpc_board.project
349
+        policy.authorize('iot:service_on_board:get', cdict, cdict)
350
+
351
+        return self._get_services_on_board_collection(rpc_board.uuid)
352
+
353
+    @expose.expose(wtypes.text, types.uuid_or_name, body=ServiceAction,
354
+                   status_code=200)
355
+    def action(self, service_ident, ServiceAction):
356
+
357
+        if not ServiceAction.action:
358
+            raise exception.MissingParameterValue(
359
+                ("Action is not specified."))
360
+
361
+        if not ServiceAction.parameters:
362
+            ServiceAction.parameters = {}
363
+
364
+        rpc_board = api_utils.get_rpc_board(self.board_ident)
365
+        rpc_service = api_utils.get_rpc_service(service_ident)
366
+
367
+        try:
368
+            cdict = pecan.request.context.to_policy_values()
369
+            cdict['owner'] = rpc_board.owner
370
+            policy.authorize('iot:service_action:post', cdict, cdict)
371
+
372
+        except exception:
373
+            return exception
374
+
375
+        rpc_board.check_if_online()
376
+
377
+        result = pecan.request.rpcapi.action_service(pecan.request.context,
378
+                                                     rpc_service.uuid,
379
+                                                     rpc_board.uuid,
380
+                                                     ServiceAction.action)
381
+        return result
382
+
383
+
285 384
 class BoardsController(rest.RestController):
286 385
     """REST controller for Boards."""
287 386
 
288 387
     _subcontroller_map = {
289 388
         'plugins': BoardPluginsController,
389
+        'services': BoardServicesController,
290 390
     }
291 391
 
292 392
     invalid_sort_key_list = ['extra', 'location']

+ 12
- 0
iotronic/common/exception.py View File

@@ -597,3 +597,15 @@ class ErrorExecutionOnBoard(IotronicException):
597 597
 
598 598
 class ServiceNotFound(NotFound):
599 599
     message = _("Service %(Service)s could not be found.")
600
+
601
+
602
+class ServiceAlreadyExists(Conflict):
603
+    message = _("A Service with UUID %(uuid)s already exists.")
604
+
605
+
606
+class ServiceAlreadyExposed(Conflict):
607
+    message = _("A Service with UUID %(uuid)s already exposed.")
608
+
609
+
610
+class ExposedServiceNotFound(NotFound):
611
+    message = _("ExposedService %(uuid)s could not be found.")

+ 15
- 0
iotronic/common/policy.py View File

@@ -136,6 +136,20 @@ service_policies = [
136 136
 
137 137
 ]
138 138
 
139
+exposed_service_policies = [
140
+    policy.RuleDefault('iot:service_on_board:get',
141
+                       'rule:admin_or_owner',
142
+                       description='Retrieve Service records'),
143
+    policy.RuleDefault('iot:service_remove:delete', 'rule:admin_or_owner',
144
+                       description='Delete Service records'),
145
+    policy.RuleDefault('iot:service_action:post',
146
+                       'rule:admin_or_owner',
147
+                       description='Create Service records'),
148
+    policy.RuleDefault('iot:service_inject:put', 'rule:admin_or_owner',
149
+                       description='Retrieve a Service record'),
150
+
151
+]
152
+
139 153
 
140 154
 def list_policies():
141 155
     policies = (default_policies
@@ -143,6 +157,7 @@ def list_policies():
143 157
                 + plugin_policies
144 158
                 + injection_plugin_policies
145 159
                 + service_policies
160
+                + exposed_service_policies
146 161
                 )
147 162
     return policies
148 163
 

+ 175
- 14
iotronic/conductor/endpoints.py View File

@@ -39,6 +39,26 @@ def get_best_agent(ctx):
39 39
     return agent.hostname
40 40
 
41 41
 
42
+def random_public_port():
43
+    return random.randint(6000, 7000)
44
+
45
+
46
+def manage_result(res, wamp_rpc_call, board_uuid):
47
+    if res.result == wm.SUCCESS:
48
+        return res.message
49
+    elif res.result == wm.WARNING:
50
+        LOG.warning('Warning in the execution of %s on %s', wamp_rpc_call,
51
+                    board_uuid)
52
+        return res.message
53
+    elif res.result == wm.ERROR:
54
+        LOG.error('Error in the execution of %s on %s: %s', wamp_rpc_call,
55
+                  board_uuid, res.message)
56
+        raise exception.ErrorExecutionOnBoard(call=wamp_rpc_call,
57
+                                              board=board_uuid,
58
+                                              error=res.message)
59
+    return res.message
60
+
61
+
42 62
 class ConductorEndpoint(object):
43 63
     def __init__(self, ragent):
44 64
         transport = oslo_messaging.get_transport(cfg.CONF)
@@ -119,10 +139,12 @@ class ConductorEndpoint(object):
119 139
                                                board_id,
120 140
                                                'destroyBoard',
121 141
                                                (p,))
142
+
122 143
             except exception:
123 144
                 return exception
124 145
         board.destroy()
125 146
         if result:
147
+            result = manage_result(result, 'destroyBoard', board_id)
126 148
             LOG.debug(result)
127 149
             return result
128 150
         return
@@ -164,18 +186,7 @@ class ConductorEndpoint(object):
164 186
                                           data=wamp_rpc_args)
165 187
         res = wm.deserialize(res)
166 188
 
167
-        if res.result == wm.SUCCESS:
168
-            return res.message
169
-        elif res.result == wm.WARNING:
170
-            LOG.warning('Warning in the execution of %s on %s', wamp_rpc_call,
171
-                        board_uuid)
172
-            return res.message
173
-        elif res.result == wm.ERROR:
174
-            LOG.error('Error in the execution of %s on %s: %s', wamp_rpc_call,
175
-                      board_uuid, res.message)
176
-            raise exception.ErrorExecutionOnBoard(call=wamp_rpc_call,
177
-                                                  board=board.uuid,
178
-                                                  error=res.message)
189
+        return res
179 190
 
180 191
     def destroy_plugin(self, ctx, plugin_id):
181 192
         LOG.info('Destroying plugin with id %s',
@@ -231,7 +242,9 @@ class ConductorEndpoint(object):
231 242
             injection = objects.InjectionPlugin(ctx, **inj_data)
232 243
             injection.create()
233 244
 
245
+        result = manage_result(result, 'PluginInject', board_uuid)
234 246
         LOG.debug(result)
247
+
235 248
         return result
236 249
 
237 250
     def remove_plugin(self, ctx, plugin_uuid, board_uuid):
@@ -247,7 +260,7 @@ class ConductorEndpoint(object):
247 260
                                            (plugin.uuid,))
248 261
         except exception:
249 262
             return exception
250
-
263
+        result = manage_result(result, 'PluginRemove', board_uuid)
251 264
         LOG.debug(result)
252 265
         injection.destroy()
253 266
         return result
@@ -267,7 +280,7 @@ class ConductorEndpoint(object):
267 280
                                                (plugin.uuid,))
268 281
         except exception:
269 282
             return exception
270
-
283
+        result = manage_result(result, action, board_uuid)
271 284
         LOG.debug(result)
272 285
         return result
273 286
 
@@ -290,3 +303,151 @@ class ConductorEndpoint(object):
290 303
         LOG.debug('Updating service %s', service.name)
291 304
         service.save()
292 305
         return serializer.serialize_entity(ctx, service)
306
+
307
+    def action_service(self, ctx, service_uuid, board_uuid, action):
308
+        LOG.info('Enable service with id %s into the board %s',
309
+                 service_uuid, board_uuid)
310
+        service = objects.Service.get(ctx, service_uuid)
311
+        objects.service.is_valid_action(action)
312
+
313
+        if action == "ServiceEnable":
314
+            try:
315
+                objects.ExposedService.get(ctx,
316
+                                           board_uuid,
317
+                                           service_uuid)
318
+                return exception.ServiceAlreadyExposed(uuid=service_uuid)
319
+            except Exception:
320
+                name = service.name
321
+                public_port = random_public_port()
322
+                port = service.port
323
+
324
+                res = self.execute_on_board(ctx, board_uuid, action,
325
+                                            (name, public_port, port))
326
+
327
+                if res.result == wm.SUCCESS:
328
+                    pid = res.message[0]
329
+
330
+                    exp_data = {
331
+                        'board_uuid': board_uuid,
332
+                        'service_uuid': service_uuid,
333
+                        'public_port': public_port,
334
+                        'pid': pid,
335
+                    }
336
+                    exposed = objects.ExposedService(ctx, **exp_data)
337
+                    exposed.create()
338
+
339
+                    res.message = res.message[1]
340
+                elif res.result == wm.ERROR:
341
+                    LOG.error('Error in the execution of %s on %s: %s',
342
+                              action,
343
+                              board_uuid, res.message)
344
+                    raise exception.ErrorExecutionOnBoard(call=action,
345
+                                                          board=board_uuid,
346
+                                                          error=res.message)
347
+                LOG.debug(res.message)
348
+                return res.message
349
+
350
+        elif action == "ServiceDisable":
351
+            exposed = objects.ExposedService.get(ctx,
352
+                                                 board_uuid,
353
+                                                 service_uuid)
354
+
355
+            res = self.execute_on_board(ctx, board_uuid, action,
356
+                                        (service.name, exposed.pid))
357
+
358
+            result = manage_result(res, action, board_uuid)
359
+            LOG.debug(res.message)
360
+            exposed.destroy()
361
+            return result
362
+
363
+        elif action == "ServiceRestore":
364
+
365
+            exposed = objects.ExposedService.get(ctx, board_uuid,
366
+                                                 service_uuid)
367
+
368
+            print(exposed)
369
+
370
+            res = self.execute_on_board(ctx, board_uuid, action,
371
+                                        (service.name, exposed.public_port,
372
+                                         service.port, exposed.pid))
373
+
374
+            if res.result == wm.SUCCESS:
375
+                pid = res.message[0]
376
+
377
+                exp_data = {
378
+                    'id': exposed.id,
379
+                    'board_uuid': board_uuid,
380
+                    'service_uuid': service_uuid,
381
+                    'public_port': exposed.public_port,
382
+                    'pid': pid,
383
+                }
384
+
385
+                exposed = objects.ExposedService(ctx, **exp_data)
386
+                exposed.save()
387
+
388
+                res.message = res.message[1]
389
+            elif res.result == wm.ERROR:
390
+                LOG.error('Error in the execution of %s on %s: %s',
391
+                          action,
392
+                          board_uuid, res.message)
393
+                raise exception.ErrorExecutionOnBoard(call=action,
394
+                                                      board=board_uuid,
395
+                                                      error=res.message)
396
+            LOG.debug(res.message)
397
+            return res.message
398
+
399
+            # try:
400
+            #
401
+            #
402
+            #     return exception.ServiceAlreadyExposed(uuid=service_uuid)
403
+            # except:
404
+            #     name=service.name
405
+            #     public_port=random_public_port()
406
+            #     port=service.port
407
+            #
408
+            #     res = self.execute_on_board(ctx, board_uuid, action,
409
+            #                                 (name, public_port, port))
410
+            #
411
+            #     if res.result == wm.SUCCESS:
412
+            #         pid = res.message[0]
413
+            #
414
+            #         exp_data = {
415
+            #             'board_uuid': board_uuid,
416
+            #             'service_uuid': service_uuid,
417
+            #             'public_port': public_port,
418
+            #             'pid': pid,
419
+            #         }
420
+            #         exposed = objects.ExposedService(ctx, **exp_data)
421
+            #         exposed.create()
422
+            #
423
+            #         res.message = res.message[1]
424
+            #     elif res.result == wm.ERROR:
425
+            #         LOG.error('Error in the execution of %s on %s: %s',
426
+            #                   action,
427
+            #                   board_uuid, res.message)
428
+            #         raise exception.ErrorExecutionOnBoard(call=action,
429
+            #                                               board=board_uuid,
430
+            #                                               error=res.message)
431
+            #     LOG.debug(res.message)
432
+            #     return res.message
433
+            #
434
+            #
435
+            #
436
+            #
437
+            #
438
+            #
439
+            #
440
+            #
441
+            #
442
+            #
443
+            #
444
+            # exposed = objects.ExposedService.get(ctx, board_uuid,
445
+            #                                          service_uuid)
446
+            #
447
+            # res = self.execute_on_board(ctx, board_uuid, action,
448
+            #                             (service.name, exposed.pid))
449
+            #
450
+            # result=manage_result(res,action,board_uuid)
451
+            # LOG.debug(res.message)
452
+            # exposed.destroy()
453
+            # return result

+ 14
- 0
iotronic/conductor/rpcapi.py View File

@@ -249,3 +249,17 @@ class ConductorAPI(object):
249 249
         """
250 250
         cctxt = self.client.prepare(topic=topic or self.topic, version='1.0')
251 251
         return cctxt.call(context, 'update_service', service_obj=service_obj)
252
+
253
+    def action_service(self, context, service_uuid,
254
+                       board_uuid, action, topic=None):
255
+        """Action on a service into a board.
256
+
257
+        :param context: request context.
258
+        :param service_uuid: service id or uuid.
259
+        :param board_uuid: board id or uuid.
260
+
261
+        """
262
+        cctxt = self.client.prepare(topic=topic or self.topic, version='1.0')
263
+
264
+        return cctxt.call(context, 'action_service', service_uuid=service_uuid,
265
+                          board_uuid=board_uuid, action=action)

+ 54
- 0
iotronic/db/api.py View File

@@ -473,3 +473,57 @@ class Connection(object):
473 473
         :raises: ServiceAssociated
474 474
         :raises: ServiceNotFound
475 475
         """
476
+
477
+    @abc.abstractmethod
478
+    def get_exposed_service_by_board_uuid(self, board_uuid):
479
+        """get an exposed of a service using a board_uuid
480
+
481
+        :param board_uuid: The id or uuid of a board.
482
+        :returns: An exposed_service.
483
+
484
+        """
485
+
486
+    @abc.abstractmethod
487
+    def get_exposed_service_by_uuids(self, board_uuid, service_uuid):
488
+        """get an exposed of a service using a board_uuid and service_uuid
489
+
490
+        :param board_uuid: The id or uuid of a board.
491
+        :param service_uuid: The id or uuid of a service.
492
+        :returns: An exposed_service.
493
+
494
+        """
495
+
496
+    @abc.abstractmethod
497
+    def create_exposed_service(self, values):
498
+        """Create a new exposed_service.
499
+
500
+        :param values: A dict containing several items used to identify
501
+                       and track the service
502
+        :returns: An exposed service.
503
+        """
504
+
505
+    @abc.abstractmethod
506
+    def destroy_exposed_service(self, exposed_service_id):
507
+        """Destroy an exposed service and all associated interfaces.
508
+
509
+        :param exposed_service_id: The id or uuid of a service.
510
+        """
511
+
512
+    @abc.abstractmethod
513
+    def update_exposed_service(self, service_exposed_id, values):
514
+        """Update properties of a service.
515
+
516
+        :param service_id: The id or uuid of a service.
517
+        :param values: Dict of values to update.
518
+        :returns: A service.
519
+        :raises: ServiceAssociated
520
+        :raises: ServiceNotFound
521
+        """
522
+
523
+    @abc.abstractmethod
524
+    def get_exposed_service_list(self, board_uuid):
525
+        """Return a list of exposed_services.
526
+
527
+        :param board_uuid: The id or uuid of a service.
528
+        :returns: A list of ExposedServices on the board.
529
+        """

+ 81
- 0
iotronic/db/sqlalchemy/api.py View File

@@ -761,3 +761,84 @@ class Connection(api.Connection):
761 761
 
762 762
             ref.update(values)
763 763
         return ref
764
+
765
+    # EXPOSED SERVICE api
766
+
767
+    def get_exposed_service_by_board_uuid(self, board_uuid):
768
+        query = model_query(
769
+            models.ExposedService).filter_by(
770
+            board_uuid=board_uuid)
771
+        try:
772
+            return query.one()
773
+        except NoResultFound:
774
+            raise exception.ExposedServiceNotFound()
775
+
776
+    def create_exposed_service(self, values):
777
+        # ensure defaults are present for new services
778
+        if 'uuid' not in values:
779
+            values['uuid'] = uuidutils.generate_uuid()
780
+        exp_serv = models.ExposedService()
781
+        exp_serv.update(values)
782
+        try:
783
+            exp_serv.save()
784
+        except db_exc.DBDuplicateEntry:
785
+            raise exception.ServiceAlreadyExposed(uuid=values['uuid'])
786
+        return exp_serv
787
+
788
+    def update_exposed_service(self, service_exposed_id, values):
789
+
790
+        if 'uuid' in values:
791
+            msg = _("Cannot overwrite UUID for an existing Service.")
792
+            raise exception.InvalidParameterValue(err=msg)
793
+        try:
794
+            return self._do_update_exposed_service(
795
+                service_exposed_id, values)
796
+
797
+        except db_exc.DBDuplicateEntry as e:
798
+            if 'name' in e.columns:
799
+                raise exception.DuplicateName(name=values['name'])
800
+            elif 'uuid' in e.columns:
801
+                raise exception.ServiceAlreadyExists(uuid=values['uuid'])
802
+            else:
803
+                raise e
804
+
805
+    def get_exposed_service_by_uuids(self, board_uuid, service_uuid):
806
+        query = model_query(
807
+            models.ExposedService).filter_by(
808
+            board_uuid=board_uuid).filter_by(
809
+            service_uuid=service_uuid)
810
+        try:
811
+            return query.one()
812
+        except NoResultFound:
813
+            raise exception.ExposedServiceNotFound(uuid=service_uuid)
814
+
815
+    def destroy_exposed_service(self, exposed_service_id):
816
+
817
+        session = get_session()
818
+        with session.begin():
819
+            query = model_query(models.ExposedService, session=session)
820
+            query = add_identity_filter(query, exposed_service_id)
821
+            try:
822
+                query.delete()
823
+
824
+            except NoResultFound:
825
+                raise exception.ExposedServiceNotFound()
826
+
827
+    def get_exposed_service_list(self, board_uuid):
828
+        query = model_query(
829
+            models.ExposedService).filter_by(
830
+            board_uuid=board_uuid)
831
+        return query.all()
832
+
833
+    def _do_update_exposed_service(self, service_id, values):
834
+        session = get_session()
835
+        with session.begin():
836
+            query = model_query(models.ExposedService, session=session)
837
+            query = add_identity_filter(query, service_id)
838
+            try:
839
+                ref = query.with_lockmode('update').one()
840
+            except NoResultFound:
841
+                raise exception.ServiceNotFoundNotFound(uuid=service_id)
842
+
843
+            ref.update(values)
844
+        return ref

+ 1
- 0
iotronic/db/sqlalchemy/models.py View File

@@ -248,3 +248,4 @@ class ExposedService(Base):
248 248
     board_uuid = Column(String(36), ForeignKey('boards.uuid'))
249 249
     service_uuid = Column(String(36), ForeignKey('services.uuid'))
250 250
     public_port = Column(Integer)
251
+    pid = Column(Integer)

+ 3
- 0
iotronic/objects/__init__.py View File

@@ -14,6 +14,7 @@
14 14
 
15 15
 from iotronic.objects import board
16 16
 from iotronic.objects import conductor
17
+from iotronic.objects import exposedservice
17 18
 from iotronic.objects import injectionplugin
18 19
 from iotronic.objects import location
19 20
 from iotronic.objects import plugin
@@ -26,6 +27,7 @@ Board = board.Board
26 27
 Location = location.Location
27 28
 Plugin = plugin.Plugin
28 29
 InjectionPlugin = injectionplugin.InjectionPlugin
30
+ExposedService = exposedservice.ExposedService
29 31
 SessionWP = sessionwp.SessionWP
30 32
 WampAgent = wampagent.WampAgent
31 33
 Service = service.Service
@@ -39,4 +41,5 @@ __all__ = (
39 41
     Service,
40 42
     Plugin,
41 43
     InjectionPlugin,
44
+    ExposedService
42 45
 )

+ 165
- 0
iotronic/objects/exposedservice.py View File

@@ -0,0 +1,165 @@
1
+# coding=utf-8
2
+#
3
+#
4
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
5
+#    not use this file except in compliance with the License. You may obtain
6
+#    a copy of the License at
7
+#
8
+#         http://www.apache.org/licenses/LICENSE-2.0
9
+#
10
+#    Unless required by applicable law or agreed to in writing, software
11
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13
+#    License for the specific language governing permissions and limitations
14
+#    under the License.
15
+
16
+from iotronic.db import api as db_api
17
+from iotronic.objects import base
18
+from iotronic.objects import utils as obj_utils
19
+
20
+
21
+class ExposedService(base.IotronicObject):
22
+    # Version 1.0: Initial version
23
+    VERSION = '1.0'
24
+
25
+    dbapi = db_api.get_instance()
26
+
27
+    fields = {
28
+        'id': int,
29
+        'board_uuid': obj_utils.str_or_none,
30
+        'service_uuid': obj_utils.str_or_none,
31
+        'public_port': int,
32
+        'pid': int
33
+    }
34
+
35
+    @staticmethod
36
+    def _from_db_object(exposed_service, db_exposed_service):
37
+        """Converts a database entity to a formal object."""
38
+        for field in exposed_service.fields:
39
+            exposed_service[field] = db_exposed_service[field]
40
+        exposed_service.obj_reset_changes()
41
+        return exposed_service
42
+
43
+    @base.remotable_classmethod
44
+    def get_by_id(cls, context, exposed_service_id):
45
+        """Find a exposed_service based on its integer id and return a Board object.
46
+
47
+        :param exposed_service_id: the id of a exposed_service.
48
+        :returns: a :class:`exposed_service` object.
49
+        """
50
+        db_exp_service = cls.dbapi.get_exposed_service_by_id(
51
+            exposed_service_id)
52
+        exp_service = ExposedService._from_db_object(cls(context),
53
+                                                     db_exp_service)
54
+        return exp_service
55
+
56
+    @base.remotable_classmethod
57
+    def get_by_board_uuid(cls, context, board_uuid):
58
+        """Find a exposed_service based on uuid and return a Board object.
59
+
60
+        :param board_uuid: the uuid of a exposed_service.
61
+        :returns: a :class:`exposed_service` object.
62
+        """
63
+        db_exp_service = cls.dbapi.get_exposed_service_by_board_uuid(
64
+            board_uuid)
65
+        exp_service = ExposedService._from_db_object(cls(context),
66
+                                                     db_exp_service)
67
+        return exp_service
68
+
69
+    @base.remotable_classmethod
70
+    def get_by_service_uuid(cls, context, service_uuid):
71
+        """Find a exposed_service based on uuid and return a Board object.
72
+
73
+        :param service_uuid: the uuid of a exposed_service.
74
+        :returns: a :class:`exposed_service` object.
75
+        """
76
+        db_exp_service = cls.dbapi.get_exposed_service_by_service_uuid(
77
+            service_uuid)
78
+        exp_service = ExposedService._from_db_object(cls(context),
79
+                                                     db_exp_service)
80
+        return exp_service
81
+
82
+    @base.remotable_classmethod
83
+    def get(cls, context, board_uuid, service_uuid):
84
+        """Find a exposed_service based on uuid and return a Service object.
85
+
86
+        :param board_uuid: the uuid of a exposed_service.
87
+        :returns: a :class:`exposed_service` object.
88
+        """
89
+        db_exp_service = cls.dbapi.get_exposed_service_by_uuids(board_uuid,
90
+                                                                service_uuid)
91
+        exp_service = ExposedService._from_db_object(cls(context),
92
+                                                     db_exp_service)
93
+        return exp_service
94
+
95
+    @base.remotable_classmethod
96
+    def list(cls, context, board_uuid):
97
+        """Return a list of ExposedService objects.
98
+
99
+        :param context: Security context.
100
+        :param limit: maximum number of resources to return in a single result.
101
+        :param marker: pagination marker for large data sets.
102
+        :param sort_key: column to sort results by.
103
+        :param sort_dir: direction to sort. "asc" or "desc".
104
+        :param filters: Filters to apply.
105
+        :returns: a list of :class:`ExposedService` object.
106
+
107
+        """
108
+        db_exps = cls.dbapi.get_exposed_service_list(board_uuid)
109
+        return [ExposedService._from_db_object(cls(context), obj)
110
+                for obj in db_exps]
111
+
112
+    @base.remotable
113
+    def create(self, context=None):
114
+        """Create a ExposedService record in the DB.
115
+
116
+        Column-wise updates will be made based on the result of
117
+        self.what_changed(). If target_power_state is provided,
118
+        it will be checked against the in-database copy of the
119
+        exposed_service before updates are made.
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.: ExposedService(context)
127
+
128
+        """
129
+        values = self.obj_get_changes()
130
+        db_exposed_service = self.dbapi.create_exposed_service(values)
131
+        self._from_db_object(self, db_exposed_service)
132
+
133
+    @base.remotable
134
+    def destroy(self, context=None):
135
+        """Delete the ExposedService from the DB.
136
+
137
+        :param context: Security context. NOTE: This should only
138
+                        be used internally by the indirection_api.
139
+                        Unfortunately, RPC requires context as the first
140
+                        argument, even though we don't use it.
141
+                        A context should be set when instantiating the
142
+                        object, e.g.: ExposedService(context)
143
+        """
144
+        self.dbapi.destroy_exposed_service(self.id)
145
+        self.obj_reset_changes()
146
+
147
+    @base.remotable
148
+    def save(self, context=None):
149
+        """Save updates to this ExposedService.
150
+
151
+        Column-wise updates will be made based on the result of
152
+        self.what_changed(). If target_power_state is provided,
153
+        it will be checked against the in-database copy of the
154
+        exposed_service before updates are made.
155
+
156
+        :param context: Security context. NOTE: This should only
157
+                        be used internally by the indirection_api.
158
+                        Unfortunately, RPC requires context as the first
159
+                        argument, even though we don't use it.
160
+                        A context should be set when instantiating the
161
+                        object, e.g.: ExposedService(context)
162
+        """
163
+        updates = self.obj_get_changes()
164
+        self.dbapi.update_exposed_service(self.id, updates)
165
+        self.obj_reset_changes()

+ 3
- 15
iotronic/objects/service.py View File

@@ -21,11 +21,8 @@ from iotronic.db import api as db_api
21 21
 from iotronic.objects import base
22 22
 from iotronic.objects import utils as obj_utils
23 23
 
24
-"""
25
-ACTIONS = ['ServiceCall', 'ServiceStop', 'ServiceStart',
26
-           'ServiceStatus', 'ServiceReboot']
27
-CUSTOM_PARAMS = ['ServiceCall', 'ServiceStart', 'ServiceReboot']
28
-NO_PARAMS = ['ServiceStatus']
24
+
25
+ACTIONS = ['ServiceEnable', 'ServiceDisable', 'ServiceRestore']
29 26
 
30 27
 
31 28
 def is_valid_action(action):
@@ -34,16 +31,6 @@ def is_valid_action(action):
34 31
     return True
35 32
 
36 33
 
37
-def want_customs_params(action):
38
-    return True if action in CUSTOM_PARAMS else False
39
-
40
-
41
-def want_params(action):
42
-    return False if action in NO_PARAMS else True
43
-
44
-"""
45
-
46
-
47 34
 class Service(base.IotronicObject):
48 35
     # Version 1.0: Initial version
49 36
     VERSION = '1.0'
@@ -154,6 +141,7 @@ class Service(base.IotronicObject):
154 141
                         object, e.g.: Service(context)
155 142
 
156 143
         """
144
+
157 145
         values = self.obj_get_changes()
158 146
         db_service = self.dbapi.create_service(values)
159 147
         self._from_db_object(self, db_service)

+ 3
- 0
utils/iotronic.sql View File

@@ -167,8 +167,11 @@ CREATE TABLE IF NOT EXISTS `iotronic`.`exposed_services` (
167 167
   `board_uuid` VARCHAR(36) NOT NULL,
168 168
   `service_uuid` VARCHAR(36) NOT NULL,
169 169
   `public_port` INT(5) NOT NULL,
170
+  `pid` INT(5) NOT NULL,
170 171
   PRIMARY KEY (`id`),
171 172
   INDEX `board_uuid` (`board_uuid` ASC),
173
+  CONSTRAINT unique_index
174
+  UNIQUE (service_uuid, board_uuid, pid),
172 175
   CONSTRAINT `fk_board_uuid`
173 176
     FOREIGN KEY (`board_uuid`)
174 177
     REFERENCES `iotronic`.`boards` (`uuid`)

Loading…
Cancel
Save