Fleets
CRUD actions for Fleets for Sebba :) Change-Id: Ia6ee145f330a844e532473784e21bc375190a707
This commit is contained in:
parent
094eae7851
commit
29f9f1ffd4
@ -25,6 +25,7 @@ from wsme import types as wtypes
|
|||||||
|
|
||||||
from iotronic.api.controllers import base
|
from iotronic.api.controllers import base
|
||||||
from iotronic.api.controllers import link
|
from iotronic.api.controllers import link
|
||||||
|
from iotronic.api.controllers.v1 import fleet
|
||||||
from iotronic.api.controllers.v1 import plugin
|
from iotronic.api.controllers.v1 import plugin
|
||||||
from iotronic.api.controllers.v1 import port
|
from iotronic.api.controllers.v1 import port
|
||||||
from iotronic.api.controllers.v1 import service
|
from iotronic.api.controllers.v1 import service
|
||||||
@ -70,6 +71,9 @@ class V1(base.APIBase):
|
|||||||
ports = [link.Link]
|
ports = [link.Link]
|
||||||
"""Links to the boards resource"""
|
"""Links to the boards resource"""
|
||||||
|
|
||||||
|
fleet = [link.Link]
|
||||||
|
"""Links to the boards resource"""
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def convert():
|
def convert():
|
||||||
v1 = V1()
|
v1 = V1()
|
||||||
@ -113,6 +117,14 @@ class V1(base.APIBase):
|
|||||||
pecan.request.public_url, 'ports', '',
|
pecan.request.public_url, 'ports', '',
|
||||||
bookmark=True)]
|
bookmark=True)]
|
||||||
|
|
||||||
|
v1.fleets = [link.Link.make_link('self', pecan.request.public_url,
|
||||||
|
'fleets', ''),
|
||||||
|
link.Link.make_link('bookmark',
|
||||||
|
pecan.request.public_url,
|
||||||
|
'fleets', '',
|
||||||
|
bookmark=True)
|
||||||
|
]
|
||||||
|
|
||||||
return v1
|
return v1
|
||||||
|
|
||||||
|
|
||||||
@ -123,6 +135,7 @@ class Controller(rest.RestController):
|
|||||||
plugins = plugin.PluginsController()
|
plugins = plugin.PluginsController()
|
||||||
services = service.ServicesController()
|
services = service.ServicesController()
|
||||||
ports = port.PortsController()
|
ports = port.PortsController()
|
||||||
|
fleets = fleet.FleetsController()
|
||||||
|
|
||||||
@expose.expose(V1)
|
@expose.expose(V1)
|
||||||
def get(self):
|
def get(self):
|
||||||
|
347
iotronic/api/controllers/v1/fleet.py
Normal file
347
iotronic/api/controllers/v1/fleet.py
Normal file
@ -0,0 +1,347 @@
|
|||||||
|
# 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 iotronic.api.controllers import base
|
||||||
|
from iotronic.api.controllers import link
|
||||||
|
from iotronic.api.controllers.v1 import collection
|
||||||
|
from iotronic.api.controllers.v1 import types
|
||||||
|
from iotronic.api.controllers.v1 import utils as api_utils
|
||||||
|
from iotronic.api import expose
|
||||||
|
from iotronic.common import exception
|
||||||
|
from iotronic.common import policy
|
||||||
|
from iotronic import objects
|
||||||
|
|
||||||
|
import pecan
|
||||||
|
from pecan import rest
|
||||||
|
import wsme
|
||||||
|
from wsme import types as wtypes
|
||||||
|
|
||||||
|
_DEFAULT_RETURN_FIELDS = (
|
||||||
|
'name', 'uuid', 'project', 'description', 'extra')
|
||||||
|
|
||||||
|
|
||||||
|
class Fleet(base.APIBase):
|
||||||
|
"""API representation of a fleet.
|
||||||
|
|
||||||
|
"""
|
||||||
|
uuid = types.uuid
|
||||||
|
name = wsme.wsattr(wtypes.text)
|
||||||
|
project = types.uuid
|
||||||
|
description = wsme.wsattr(wtypes.text)
|
||||||
|
extra = types.jsontype
|
||||||
|
|
||||||
|
links = wsme.wsattr([link.Link], readonly=True)
|
||||||
|
|
||||||
|
def __init__(self, **kwargs):
|
||||||
|
self.fields = []
|
||||||
|
fields = list(objects.Fleet.fields)
|
||||||
|
for k in fields:
|
||||||
|
# Skip fields we do not expose.
|
||||||
|
if not hasattr(self, k):
|
||||||
|
continue
|
||||||
|
self.fields.append(k)
|
||||||
|
setattr(self, k, kwargs.get(k, wtypes.Unset))
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _convert_with_links(fleet, url, fields=None):
|
||||||
|
fleet_uuid = fleet.uuid
|
||||||
|
if fields is not None:
|
||||||
|
fleet.unset_fields_except(fields)
|
||||||
|
|
||||||
|
fleet.links = [link.Link.make_link('self', url, 'fleets',
|
||||||
|
fleet_uuid),
|
||||||
|
link.Link.make_link('bookmark', url, 'fleets',
|
||||||
|
fleet_uuid, bookmark=True)
|
||||||
|
]
|
||||||
|
return fleet
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def convert_with_links(cls, rpc_fleet, fields=None):
|
||||||
|
fleet = Fleet(**rpc_fleet.as_dict())
|
||||||
|
|
||||||
|
if fields is not None:
|
||||||
|
api_utils.check_for_invalid_fields(fields, fleet.as_dict())
|
||||||
|
|
||||||
|
return cls._convert_with_links(fleet, pecan.request.public_url,
|
||||||
|
fields=fields)
|
||||||
|
|
||||||
|
|
||||||
|
class FleetCollection(collection.Collection):
|
||||||
|
"""API representation of a collection of fleets."""
|
||||||
|
|
||||||
|
fleets = [Fleet]
|
||||||
|
"""A list containing fleets objects"""
|
||||||
|
|
||||||
|
def __init__(self, **kwargs):
|
||||||
|
self._type = 'fleets'
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def convert_with_links(fleets, limit, url=None, fields=None, **kwargs):
|
||||||
|
collection = FleetCollection()
|
||||||
|
collection.fleets = [Fleet.convert_with_links(n, fields=fields)
|
||||||
|
for n in fleets]
|
||||||
|
collection.next = collection.get_next(limit, url=url, **kwargs)
|
||||||
|
return collection
|
||||||
|
|
||||||
|
|
||||||
|
class PublicFleetsController(rest.RestController):
|
||||||
|
"""REST controller for Public Fleets."""
|
||||||
|
|
||||||
|
invalid_sort_key_list = ['extra']
|
||||||
|
|
||||||
|
def _get_fleets_collection(self, marker, limit,
|
||||||
|
sort_key, sort_dir,
|
||||||
|
fields=None):
|
||||||
|
|
||||||
|
limit = api_utils.validate_limit(limit)
|
||||||
|
sort_dir = api_utils.validate_sort_dir(sort_dir)
|
||||||
|
|
||||||
|
marker_obj = None
|
||||||
|
if marker:
|
||||||
|
marker_obj = objects.Fleet.get_by_uuid(pecan.request.context,
|
||||||
|
marker)
|
||||||
|
|
||||||
|
if sort_key in self.invalid_sort_key_list:
|
||||||
|
raise exception.InvalidParameterValue(
|
||||||
|
("The sort_key value %(key)s is an invalid field for "
|
||||||
|
"sorting") % {'key': sort_key})
|
||||||
|
|
||||||
|
filters = {}
|
||||||
|
filters['public'] = True
|
||||||
|
|
||||||
|
fleets = objects.Fleet.list(pecan.request.context, limit,
|
||||||
|
marker_obj,
|
||||||
|
sort_key=sort_key, sort_dir=sort_dir,
|
||||||
|
filters=filters)
|
||||||
|
|
||||||
|
parameters = {'sort_key': sort_key, 'sort_dir': sort_dir}
|
||||||
|
|
||||||
|
return FleetCollection.convert_with_links(fleets, limit,
|
||||||
|
fields=fields,
|
||||||
|
**parameters)
|
||||||
|
|
||||||
|
@expose.expose(FleetCollection, types.uuid, int, wtypes.text,
|
||||||
|
wtypes.text, types.listtype, types.boolean, types.boolean)
|
||||||
|
def get_all(self, marker=None,
|
||||||
|
limit=None, sort_key='id', sort_dir='asc',
|
||||||
|
fields=None):
|
||||||
|
"""Retrieve a list of fleets.
|
||||||
|
|
||||||
|
:param marker: pagination marker for large data sets.
|
||||||
|
:param limit: maximum number of resources to return in a single result.
|
||||||
|
This value cannot be larger than the value of max_limit
|
||||||
|
in the [api] section of the ironic configuration, or only
|
||||||
|
max_limit resources will be returned.
|
||||||
|
:param sort_key: column to sort results by. Default: id.
|
||||||
|
:param sort_dir: direction to sort. "asc" or "desc". Default: asc.
|
||||||
|
:param fields: Optional, a list with a specified set of fields
|
||||||
|
of the resource to be returned.
|
||||||
|
"""
|
||||||
|
cdict = pecan.request.context.to_policy_values()
|
||||||
|
policy.authorize('iot:fleet:get', cdict, cdict)
|
||||||
|
|
||||||
|
if fields is None:
|
||||||
|
fields = _DEFAULT_RETURN_FIELDS
|
||||||
|
return self._get_fleets_collection(marker,
|
||||||
|
limit, sort_key, sort_dir,
|
||||||
|
fields=fields)
|
||||||
|
|
||||||
|
|
||||||
|
class FleetsController(rest.RestController):
|
||||||
|
"""REST controller for Fleets."""
|
||||||
|
|
||||||
|
public = PublicFleetsController()
|
||||||
|
|
||||||
|
invalid_sort_key_list = ['extra', ]
|
||||||
|
|
||||||
|
_custom_actions = {
|
||||||
|
'detail': ['GET'],
|
||||||
|
}
|
||||||
|
|
||||||
|
def _get_fleets_collection(self, marker, limit,
|
||||||
|
sort_key, sort_dir,
|
||||||
|
fields=None):
|
||||||
|
|
||||||
|
limit = api_utils.validate_limit(limit)
|
||||||
|
sort_dir = api_utils.validate_sort_dir(sort_dir)
|
||||||
|
|
||||||
|
marker_obj = None
|
||||||
|
if marker:
|
||||||
|
marker_obj = objects.Fleet.get_by_uuid(pecan.request.context,
|
||||||
|
marker)
|
||||||
|
|
||||||
|
if sort_key in self.invalid_sort_key_list:
|
||||||
|
raise exception.InvalidParameterValue(
|
||||||
|
("The sort_key value %(key)s is an invalid field for "
|
||||||
|
"sorting") % {'key': sort_key})
|
||||||
|
|
||||||
|
filters = {}
|
||||||
|
fleets = objects.Fleet.list(pecan.request.context, limit,
|
||||||
|
marker_obj,
|
||||||
|
sort_key=sort_key, sort_dir=sort_dir,
|
||||||
|
filters=filters)
|
||||||
|
|
||||||
|
parameters = {'sort_key': sort_key, 'sort_dir': sort_dir}
|
||||||
|
|
||||||
|
return FleetCollection.convert_with_links(fleets, limit,
|
||||||
|
fields=fields,
|
||||||
|
**parameters)
|
||||||
|
|
||||||
|
@expose.expose(Fleet, types.uuid_or_name, types.listtype)
|
||||||
|
def get_one(self, fleet_ident, fields=None):
|
||||||
|
"""Retrieve information about the given fleet.
|
||||||
|
|
||||||
|
:param fleet_ident: UUID or logical name of a fleet.
|
||||||
|
:param fields: Optional, a list with a specified set of fields
|
||||||
|
of the resource to be returned.
|
||||||
|
"""
|
||||||
|
|
||||||
|
rpc_fleet = api_utils.get_rpc_fleet(fleet_ident)
|
||||||
|
cdict = pecan.request.context.to_policy_values()
|
||||||
|
cdict['project'] = rpc_fleet.project
|
||||||
|
policy.authorize('iot:fleet:get_one', cdict, cdict)
|
||||||
|
|
||||||
|
return Fleet.convert_with_links(rpc_fleet, fields=fields)
|
||||||
|
|
||||||
|
@expose.expose(FleetCollection, types.uuid, int, wtypes.text,
|
||||||
|
wtypes.text, types.listtype, types.boolean, types.boolean)
|
||||||
|
def get_all(self, marker=None,
|
||||||
|
limit=None, sort_key='id', sort_dir='asc',
|
||||||
|
fields=None):
|
||||||
|
"""Retrieve a list of fleets.
|
||||||
|
|
||||||
|
:param marker: pagination marker for large data sets.
|
||||||
|
:param limit: maximum number of resources to return in a single result.
|
||||||
|
This value cannot be larger than the value of max_limit
|
||||||
|
in the [api] section of the ironic configuration, or only
|
||||||
|
max_limit resources will be returned.
|
||||||
|
:param sort_key: column to sort results by. Default: id.
|
||||||
|
:param sort_dir: direction to sort. "asc" or "desc". Default: asc.
|
||||||
|
:param with_public: Optional boolean to get also public pluings.
|
||||||
|
:param all_fleets: Optional boolean to get all the pluings.
|
||||||
|
Only for the admin
|
||||||
|
:param fields: Optional, a list with a specified set of fields
|
||||||
|
of the resource to be returned.
|
||||||
|
"""
|
||||||
|
cdict = pecan.request.context.to_policy_values()
|
||||||
|
policy.authorize('iot:fleet:get', cdict, cdict)
|
||||||
|
|
||||||
|
if fields is None:
|
||||||
|
fields = _DEFAULT_RETURN_FIELDS
|
||||||
|
return self._get_fleets_collection(marker,
|
||||||
|
limit, sort_key, sort_dir,
|
||||||
|
fields=fields)
|
||||||
|
|
||||||
|
@expose.expose(Fleet, body=Fleet, status_code=201)
|
||||||
|
def post(self, Fleet):
|
||||||
|
"""Create a new Fleet.
|
||||||
|
|
||||||
|
:param Fleet: a Fleet within the request body.
|
||||||
|
"""
|
||||||
|
context = pecan.request.context
|
||||||
|
cdict = context.to_policy_values()
|
||||||
|
policy.authorize('iot:fleet:create', cdict, cdict)
|
||||||
|
|
||||||
|
if not Fleet.name:
|
||||||
|
raise exception.MissingParameterValue(
|
||||||
|
("Name is not specified."))
|
||||||
|
|
||||||
|
if Fleet.name:
|
||||||
|
if not api_utils.is_valid_name(Fleet.name):
|
||||||
|
msg = ("Cannot create fleet with invalid name %(name)s")
|
||||||
|
raise wsme.exc.ClientSideError(msg % {'name': Fleet.name},
|
||||||
|
status_code=400)
|
||||||
|
|
||||||
|
new_Fleet = objects.Fleet(pecan.request.context,
|
||||||
|
**Fleet.as_dict())
|
||||||
|
|
||||||
|
new_Fleet.project = cdict['project_id']
|
||||||
|
new_Fleet = pecan.request.rpcapi.create_fleet(
|
||||||
|
pecan.request.context,
|
||||||
|
new_Fleet)
|
||||||
|
|
||||||
|
return Fleet.convert_with_links(new_Fleet)
|
||||||
|
|
||||||
|
@expose.expose(None, types.uuid_or_name, status_code=204)
|
||||||
|
def delete(self, fleet_ident):
|
||||||
|
"""Delete a fleet.
|
||||||
|
|
||||||
|
:param fleet_ident: UUID or logical name of a fleet.
|
||||||
|
"""
|
||||||
|
context = pecan.request.context
|
||||||
|
cdict = context.to_policy_values()
|
||||||
|
policy.authorize('iot:fleet:delete', cdict, cdict)
|
||||||
|
|
||||||
|
rpc_fleet = api_utils.get_rpc_fleet(fleet_ident)
|
||||||
|
pecan.request.rpcapi.destroy_fleet(pecan.request.context,
|
||||||
|
rpc_fleet.uuid)
|
||||||
|
|
||||||
|
@expose.expose(Fleet, types.uuid_or_name, body=Fleet, status_code=200)
|
||||||
|
def patch(self, fleet_ident, val_Fleet):
|
||||||
|
"""Update a fleet.
|
||||||
|
|
||||||
|
:param fleet_ident: UUID or logical name of a fleet.
|
||||||
|
:param Fleet: values to be changed
|
||||||
|
:return updated_fleet: updated_fleet
|
||||||
|
"""
|
||||||
|
|
||||||
|
rpc_fleet = api_utils.get_rpc_fleet(fleet_ident)
|
||||||
|
cdict = pecan.request.context.to_policy_values()
|
||||||
|
cdict['project'] = rpc_fleet.project
|
||||||
|
policy.authorize('iot:fleet:update', cdict, cdict)
|
||||||
|
|
||||||
|
val_Fleet = val_Fleet.as_dict()
|
||||||
|
for key in val_Fleet:
|
||||||
|
try:
|
||||||
|
rpc_fleet[key] = val_Fleet[key]
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
updated_fleet = pecan.request.rpcapi.update_fleet(
|
||||||
|
pecan.request.context, rpc_fleet)
|
||||||
|
return Fleet.convert_with_links(updated_fleet)
|
||||||
|
|
||||||
|
@expose.expose(FleetCollection, types.uuid, int, wtypes.text,
|
||||||
|
wtypes.text, types.listtype, types.boolean, types.boolean)
|
||||||
|
def detail(self, marker=None,
|
||||||
|
limit=None, sort_key='id', sort_dir='asc',
|
||||||
|
fields=None, with_public=False, all_fleets=False):
|
||||||
|
"""Retrieve a list of fleets.
|
||||||
|
|
||||||
|
:param marker: pagination marker for large data sets.
|
||||||
|
:param limit: maximum number of resources to return in a single result.
|
||||||
|
This value cannot be larger than the value of max_limit
|
||||||
|
in the [api] section of the ironic configuration, or only
|
||||||
|
max_limit resources will be returned.
|
||||||
|
:param sort_key: column to sort results by. Default: id.
|
||||||
|
:param sort_dir: direction to sort. "asc" or "desc". Default: asc.
|
||||||
|
:param with_public: Optional boolean to get also public fleet.
|
||||||
|
:param all_fleets: Optional boolean to get all the fleets.
|
||||||
|
Only for the admin
|
||||||
|
:param fields: Optional, a list with a specified set of fields
|
||||||
|
of the resource to be returned.
|
||||||
|
"""
|
||||||
|
|
||||||
|
cdict = pecan.request.context.to_policy_values()
|
||||||
|
policy.authorize('iot:fleet:get', cdict, cdict)
|
||||||
|
|
||||||
|
# /detail should only work against collections
|
||||||
|
parent = pecan.request.path.split('/')[:-1][-1]
|
||||||
|
if parent != "fleets":
|
||||||
|
raise exception.HTTPNotFound()
|
||||||
|
|
||||||
|
return self._get_fleets_collection(marker,
|
||||||
|
limit, sort_key, sort_dir,
|
||||||
|
with_public=with_public,
|
||||||
|
all_fleets=all_fleets,
|
||||||
|
fields=fields)
|
@ -156,13 +156,13 @@ def get_rpc_port(port_ident):
|
|||||||
:raises: InvalidUuidOrName if the name or uuid provided is not valid.
|
:raises: InvalidUuidOrName if the name or uuid provided is not valid.
|
||||||
:raises: portNotFound if the port is not found.
|
:raises: portNotFound if the port is not found.
|
||||||
"""
|
"""
|
||||||
# Check to see if the port_ident is a valid UUID. If it is, treat it
|
# Check to see if the port_ident is a valid UUID. If it is, treat it
|
||||||
# as a UUID.
|
# as a UUID.
|
||||||
if uuidutils.is_uuid_like(port_ident):
|
if uuidutils.is_uuid_like(port_ident):
|
||||||
return objects.Port.get_by_uuid(pecan.request.context,
|
return objects.Port.get_by_uuid(pecan.request.context,
|
||||||
port_ident)
|
port_ident)
|
||||||
|
|
||||||
# We can refer to ports by their name, if the client supports it
|
# We can refer to ports by their name, if the client supports it
|
||||||
else:
|
else:
|
||||||
return objects.Port.get_by_name(pecan.request.context,
|
return objects.Port.get_by_name(pecan.request.context,
|
||||||
port_ident)
|
port_ident)
|
||||||
@ -172,6 +172,33 @@ def get_rpc_port(port_ident):
|
|||||||
raise exception.PortNottFound(uuid=port_ident)
|
raise exception.PortNottFound(uuid=port_ident)
|
||||||
|
|
||||||
|
|
||||||
|
def get_rpc_fleet(fleet_ident):
|
||||||
|
"""Get the RPC fleet from the fleet uuid or logical name.
|
||||||
|
|
||||||
|
:param fleet_ident: the UUID or logical name of a fleet.
|
||||||
|
|
||||||
|
:returns: The RPC Fleet.
|
||||||
|
:raises: InvalidUuidOrName if the name or uuid provided is not valid.
|
||||||
|
:raises: FleetNotFound if the fleet is not found.
|
||||||
|
"""
|
||||||
|
# Check to see if the fleet_ident is a valid UUID. If it is, treat it
|
||||||
|
# as a UUID.
|
||||||
|
if uuidutils.is_uuid_like(fleet_ident):
|
||||||
|
return objects.Fleet.get_by_uuid(pecan.request.context,
|
||||||
|
fleet_ident)
|
||||||
|
|
||||||
|
# We can refer to fleets by their name, if the client supports it
|
||||||
|
# if allow_fleet_logical_names():
|
||||||
|
# if utils.is_hostname_safe(fleet_ident):
|
||||||
|
else:
|
||||||
|
return objects.Fleet.get_by_name(pecan.request.context,
|
||||||
|
fleet_ident)
|
||||||
|
|
||||||
|
raise exception.InvalidUuidOrName(name=fleet_ident)
|
||||||
|
|
||||||
|
raise exception.FleetNotFound(fleet=fleet_ident)
|
||||||
|
|
||||||
|
|
||||||
def is_valid_board_name(name):
|
def is_valid_board_name(name):
|
||||||
"""Determine if the provided name is a valid board name.
|
"""Determine if the provided name is a valid board name.
|
||||||
|
|
||||||
|
@ -629,3 +629,15 @@ class NetworkError(IotronicException):
|
|||||||
|
|
||||||
class DatabaseVersionTooOld(IotronicException):
|
class DatabaseVersionTooOld(IotronicException):
|
||||||
_msg_fmt = _("Database version is too old")
|
_msg_fmt = _("Database version is too old")
|
||||||
|
|
||||||
|
|
||||||
|
class FleetNotFound(NotFound):
|
||||||
|
message = _("Fleet %(Fleet)s could not be found.")
|
||||||
|
|
||||||
|
|
||||||
|
class FleetAlreadyExists(Conflict):
|
||||||
|
message = _("A Fleet with UUID %(uuid)s already exists.")
|
||||||
|
|
||||||
|
|
||||||
|
class FleetAlreadyExposed(Conflict):
|
||||||
|
message = _("A Fleet with UUID %(uuid)s already exposed.")
|
||||||
|
@ -104,7 +104,6 @@ plugin_policies = [
|
|||||||
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
injection_plugin_policies = [
|
injection_plugin_policies = [
|
||||||
policy.RuleDefault('iot:plugin_on_board:get',
|
policy.RuleDefault('iot:plugin_on_board:get',
|
||||||
'rule:admin_or_owner',
|
'rule:admin_or_owner',
|
||||||
@ -166,6 +165,22 @@ exposed_service_policies = [
|
|||||||
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
fleet_policies = [
|
||||||
|
policy.RuleDefault('iot:fleet:get',
|
||||||
|
'rule:is_admin or rule:is_iot_member',
|
||||||
|
description='Retrieve Fleet records'),
|
||||||
|
policy.RuleDefault('iot:fleet:create',
|
||||||
|
'rule:is_iot_member',
|
||||||
|
description='Create Fleet records'),
|
||||||
|
policy.RuleDefault('iot:fleet:get_one', 'rule:admin_or_owner',
|
||||||
|
description='Retrieve a Fleet record'),
|
||||||
|
policy.RuleDefault('iot:fleet:delete', 'rule:admin_or_owner',
|
||||||
|
description='Delete Fleet records'),
|
||||||
|
policy.RuleDefault('iot:fleet:update', 'rule:admin_or_owner',
|
||||||
|
description='Update Fleet records'),
|
||||||
|
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
def list_policies():
|
def list_policies():
|
||||||
policies = (default_policies
|
policies = (default_policies
|
||||||
@ -175,6 +190,7 @@ def list_policies():
|
|||||||
+ service_policies
|
+ service_policies
|
||||||
+ exposed_service_policies
|
+ exposed_service_policies
|
||||||
+ port_on_board_policies
|
+ port_on_board_policies
|
||||||
|
+ fleet_policies
|
||||||
)
|
)
|
||||||
return policies
|
return policies
|
||||||
|
|
||||||
|
@ -26,7 +26,6 @@ from oslo_log import log as logging
|
|||||||
import oslo_messaging
|
import oslo_messaging
|
||||||
import random
|
import random
|
||||||
|
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
serializer = objects_base.IotronicObjectSerializer()
|
serializer = objects_base.IotronicObjectSerializer()
|
||||||
@ -489,3 +488,23 @@ class ConductorEndpoint(object):
|
|||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
LOG.error(str(e))
|
LOG.error(str(e))
|
||||||
|
|
||||||
|
def create_fleet(self, ctx, fleet_obj):
|
||||||
|
new_fleet = serializer.deserialize_entity(ctx, fleet_obj)
|
||||||
|
LOG.debug('Creating fleet %s',
|
||||||
|
new_fleet.name)
|
||||||
|
new_fleet.create()
|
||||||
|
return serializer.serialize_entity(ctx, new_fleet)
|
||||||
|
|
||||||
|
def destroy_fleet(self, ctx, fleet_id):
|
||||||
|
LOG.info('Destroying fleet with id %s',
|
||||||
|
fleet_id)
|
||||||
|
fleet = objects.Fleet.get_by_uuid(ctx, fleet_id)
|
||||||
|
fleet.destroy()
|
||||||
|
return
|
||||||
|
|
||||||
|
def update_fleet(self, ctx, fleet_obj):
|
||||||
|
fleet = serializer.deserialize_entity(ctx, fleet_obj)
|
||||||
|
LOG.debug('Updating fleet %s', fleet.name)
|
||||||
|
fleet.save()
|
||||||
|
return serializer.serialize_entity(ctx, fleet)
|
||||||
|
@ -309,3 +309,45 @@ class ConductorAPI(object):
|
|||||||
return cctxt.call(context, 'remove_VIF_from_board',
|
return cctxt.call(context, 'remove_VIF_from_board',
|
||||||
board_uuid=board_uuid,
|
board_uuid=board_uuid,
|
||||||
port_uuid=port_uuid)
|
port_uuid=port_uuid)
|
||||||
|
|
||||||
|
def create_fleet(self, context, fleet_obj, topic=None):
|
||||||
|
"""Add a fleet on the cloud
|
||||||
|
|
||||||
|
:param context: request context.
|
||||||
|
:param fleet_obj: a changed (but not saved) fleet object.
|
||||||
|
:param topic: RPC topic. Defaults to self.topic.
|
||||||
|
:returns: created fleet object
|
||||||
|
|
||||||
|
"""
|
||||||
|
cctxt = self.client.prepare(topic=topic or self.topic, version='1.0')
|
||||||
|
return cctxt.call(context, 'create_fleet',
|
||||||
|
fleet_obj=fleet_obj)
|
||||||
|
|
||||||
|
def destroy_fleet(self, context, fleet_id, topic=None):
|
||||||
|
"""Delete a fleet.
|
||||||
|
|
||||||
|
:param context: request context.
|
||||||
|
:param fleet_id: fleet id or uuid.
|
||||||
|
:raises: FleetLocked if fleet is locked by another conductor.
|
||||||
|
:raises: FleetAssociated if the fleet contains an instance
|
||||||
|
associated with it.
|
||||||
|
:raises: InvalidState if the fleet is in the wrong provision
|
||||||
|
state to perform deletion.
|
||||||
|
"""
|
||||||
|
cctxt = self.client.prepare(topic=topic or self.topic, version='1.0')
|
||||||
|
return cctxt.call(context, 'destroy_fleet', fleet_id=fleet_id)
|
||||||
|
|
||||||
|
def update_fleet(self, context, fleet_obj, topic=None):
|
||||||
|
"""Synchronously, have a conductor update the fleet's information.
|
||||||
|
|
||||||
|
Update the fleet's information in the database and
|
||||||
|
return a fleet object.
|
||||||
|
|
||||||
|
:param context: request context.
|
||||||
|
:param fleet_obj: a changed (but not saved) fleet object.
|
||||||
|
:param topic: RPC topic. Defaults to self.topic.
|
||||||
|
:returns: updated fleet object, including all fields.
|
||||||
|
|
||||||
|
"""
|
||||||
|
cctxt = self.client.prepare(topic=topic or self.topic, version='1.0')
|
||||||
|
return cctxt.call(context, 'update_fleet', fleet_obj=fleet_obj)
|
||||||
|
@ -575,3 +575,54 @@ class Connection(object):
|
|||||||
|
|
||||||
:param port_uuid: The uuid of a port.
|
:param port_uuid: The uuid of a port.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def get_fleet_by_id(self, fleet_id):
|
||||||
|
"""Return a fleet.
|
||||||
|
|
||||||
|
:param fleet_id: The id of a fleet.
|
||||||
|
:returns: A fleet.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def get_fleet_by_uuid(self, fleet_uuid):
|
||||||
|
"""Return a fleet.
|
||||||
|
|
||||||
|
:param fleet_uuid: The uuid of a fleet.
|
||||||
|
:returns: A fleet.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def get_fleet_by_name(self, fleet_name):
|
||||||
|
"""Return a fleet.
|
||||||
|
|
||||||
|
:param fleet_name: The logical name of a fleet.
|
||||||
|
:returns: A fleet.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def create_fleet(self, values):
|
||||||
|
"""Create a new fleet.
|
||||||
|
|
||||||
|
:param values: A dict containing several items used to identify
|
||||||
|
and track the fleet
|
||||||
|
:returns: A fleet.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def destroy_fleet(self, fleet_id):
|
||||||
|
"""Destroy a fleet and all associated interfaces.
|
||||||
|
|
||||||
|
:param fleet_id: The id or uuid of a fleet.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def update_fleet(self, fleet_id, values):
|
||||||
|
"""Update properties of a fleet.
|
||||||
|
|
||||||
|
:param fleet_id: The id or uuid of a fleet.
|
||||||
|
:param values: Dict of values to update.
|
||||||
|
:returns: A fleet.
|
||||||
|
:raises: FleetAssociated
|
||||||
|
:raises: FleetNotFound
|
||||||
|
"""
|
||||||
|
@ -0,0 +1,37 @@
|
|||||||
|
# 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.
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = 'b578199e4e64'
|
||||||
|
down_revision = 'df35e9cbeaff'
|
||||||
|
|
||||||
|
from alembic import op
|
||||||
|
import iotronic.db.sqlalchemy.models
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
op.create_table('fleets',
|
||||||
|
sa.Column('created_at', sa.DateTime(), nullable=True),
|
||||||
|
sa.Column('updated_at', sa.DateTime(), nullable=True),
|
||||||
|
sa.Column('id', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('uuid', sa.String(length=36), nullable=True),
|
||||||
|
sa.Column('name', sa.String(length=36), nullable=True),
|
||||||
|
sa.Column('project', sa.String(length=36), nullable=True),
|
||||||
|
sa.Column('description', sa.String(length=300),
|
||||||
|
nullable=True),
|
||||||
|
sa.Column('extra',
|
||||||
|
iotronic.db.sqlalchemy.models.JSONEncodedDict(),
|
||||||
|
nullable=True),
|
||||||
|
sa.PrimaryKeyConstraint('id'),
|
||||||
|
sa.UniqueConstraint('uuid', name='uniq_fleets0uuid')
|
||||||
|
)
|
@ -161,6 +161,14 @@ class Connection(api.Connection):
|
|||||||
query = query.filter(models.Plugin.owner == filters['owner'])
|
query = query.filter(models.Plugin.owner == filters['owner'])
|
||||||
return query
|
return query
|
||||||
|
|
||||||
|
def _add_fleets_filters(self, query, filters):
|
||||||
|
if filters is None:
|
||||||
|
filters = []
|
||||||
|
|
||||||
|
if 'project' in filters:
|
||||||
|
query = query.filter(models.Fleet.project == filters['project'])
|
||||||
|
return query
|
||||||
|
|
||||||
def _add_wampagents_filters(self, query, filters):
|
def _add_wampagents_filters(self, query, filters):
|
||||||
if filters is None:
|
if filters is None:
|
||||||
filters = []
|
filters = []
|
||||||
@ -927,3 +935,92 @@ class Connection(api.Connection):
|
|||||||
count = query.delete()
|
count = query.delete()
|
||||||
if count == 0:
|
if count == 0:
|
||||||
raise exception.PortNotFound(uuid=uuid)
|
raise exception.PortNotFound(uuid=uuid)
|
||||||
|
|
||||||
|
# FLEET api
|
||||||
|
|
||||||
|
def get_fleet_by_id(self, fleet_id):
|
||||||
|
query = model_query(models.Fleet).filter_by(id=fleet_id)
|
||||||
|
try:
|
||||||
|
return query.one()
|
||||||
|
except NoResultFound:
|
||||||
|
raise exception.FleetNotFound(fleet=fleet_id)
|
||||||
|
|
||||||
|
def get_fleet_by_uuid(self, fleet_uuid):
|
||||||
|
query = model_query(models.Fleet).filter_by(uuid=fleet_uuid)
|
||||||
|
try:
|
||||||
|
return query.one()
|
||||||
|
except NoResultFound:
|
||||||
|
raise exception.FleetNotFound(fleet=fleet_uuid)
|
||||||
|
|
||||||
|
def get_fleet_by_name(self, fleet_name):
|
||||||
|
query = model_query(models.Fleet).filter_by(name=fleet_name)
|
||||||
|
try:
|
||||||
|
return query.one()
|
||||||
|
except NoResultFound:
|
||||||
|
raise exception.FleetNotFound(fleet=fleet_name)
|
||||||
|
|
||||||
|
def destroy_fleet(self, fleet_id):
|
||||||
|
|
||||||
|
session = get_session()
|
||||||
|
with session.begin():
|
||||||
|
query = model_query(models.Fleet, session=session)
|
||||||
|
query = add_identity_filter(query, fleet_id)
|
||||||
|
try:
|
||||||
|
fleet_ref = query.one()
|
||||||
|
except NoResultFound:
|
||||||
|
raise exception.FleetNotFound(fleet=fleet_id)
|
||||||
|
|
||||||
|
# Get fleet ID, if an UUID was supplied. The ID is
|
||||||
|
# required for deleting all ports, attached to the fleet.
|
||||||
|
if uuidutils.is_uuid_like(fleet_id):
|
||||||
|
fleet_id = fleet_ref['id']
|
||||||
|
|
||||||
|
query.delete()
|
||||||
|
|
||||||
|
def update_fleet(self, fleet_id, values):
|
||||||
|
# NOTE(dtantsur): this can lead to very strange errors
|
||||||
|
if 'uuid' in values:
|
||||||
|
msg = _("Cannot overwrite UUID for an existing Fleet.")
|
||||||
|
raise exception.InvalidParameterValue(err=msg)
|
||||||
|
|
||||||
|
try:
|
||||||
|
return self._do_update_fleet(fleet_id, values)
|
||||||
|
except db_exc.DBDuplicateEntry as e:
|
||||||
|
if 'name' in e.columns:
|
||||||
|
raise exception.DuplicateName(name=values['name'])
|
||||||
|
elif 'uuid' in e.columns:
|
||||||
|
raise exception.FleetAlreadyExists(uuid=values['uuid'])
|
||||||
|
else:
|
||||||
|
raise e
|
||||||
|
|
||||||
|
def create_fleet(self, values):
|
||||||
|
# ensure defaults are present for new fleets
|
||||||
|
if 'uuid' not in values:
|
||||||
|
values['uuid'] = uuidutils.generate_uuid()
|
||||||
|
fleet = models.Fleet()
|
||||||
|
fleet.update(values)
|
||||||
|
try:
|
||||||
|
fleet.save()
|
||||||
|
except db_exc.DBDuplicateEntry:
|
||||||
|
raise exception.FleetAlreadyExists(uuid=values['uuid'])
|
||||||
|
return fleet
|
||||||
|
|
||||||
|
def get_fleet_list(self, filters=None, limit=None, marker=None,
|
||||||
|
sort_key=None, sort_dir=None):
|
||||||
|
query = model_query(models.Fleet)
|
||||||
|
query = self._add_fleets_filters(query, filters)
|
||||||
|
return _paginate_query(models.Fleet, limit, marker,
|
||||||
|
sort_key, sort_dir, query)
|
||||||
|
|
||||||
|
def _do_update_fleet(self, fleet_id, values):
|
||||||
|
session = get_session()
|
||||||
|
with session.begin():
|
||||||
|
query = model_query(models.Fleet, session=session)
|
||||||
|
query = add_identity_filter(query, fleet_id)
|
||||||
|
try:
|
||||||
|
ref = query.with_lockmode('update').one()
|
||||||
|
except NoResultFound:
|
||||||
|
raise exception.FleetNotFound(fleet=fleet_id)
|
||||||
|
|
||||||
|
ref.update(values)
|
||||||
|
return ref
|
||||||
|
@ -264,4 +264,20 @@ class Port(Base):
|
|||||||
ip = Column(String(36))
|
ip = Column(String(36))
|
||||||
# status = Column(String(36))
|
# status = Column(String(36))
|
||||||
network = Column(String(36))
|
network = Column(String(36))
|
||||||
|
|
||||||
|
|
||||||
# security_groups = Column(String(40))
|
# security_groups = Column(String(40))
|
||||||
|
|
||||||
|
class Fleet(Base):
|
||||||
|
"""Represents a fleet."""
|
||||||
|
|
||||||
|
__tablename__ = 'fleets'
|
||||||
|
__table_args__ = (
|
||||||
|
schema.UniqueConstraint('uuid', name='uniq_fleets0uuid'),
|
||||||
|
table_args())
|
||||||
|
id = Column(Integer, primary_key=True)
|
||||||
|
uuid = Column(String(36))
|
||||||
|
name = Column(String(36))
|
||||||
|
project = Column(String(36))
|
||||||
|
description = Column(String(300))
|
||||||
|
extra = Column(JSONEncodedDict)
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
from iotronic.objects import board
|
from iotronic.objects import board
|
||||||
from iotronic.objects import conductor
|
from iotronic.objects import conductor
|
||||||
from iotronic.objects import exposedservice
|
from iotronic.objects import exposedservice
|
||||||
|
from iotronic.objects import fleet
|
||||||
from iotronic.objects import injectionplugin
|
from iotronic.objects import injectionplugin
|
||||||
from iotronic.objects import location
|
from iotronic.objects import location
|
||||||
from iotronic.objects import plugin
|
from iotronic.objects import plugin
|
||||||
@ -33,6 +34,7 @@ SessionWP = sessionwp.SessionWP
|
|||||||
WampAgent = wampagent.WampAgent
|
WampAgent = wampagent.WampAgent
|
||||||
Service = service.Service
|
Service = service.Service
|
||||||
Port = port.Port
|
Port = port.Port
|
||||||
|
Fleet = fleet.Fleet
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
Conductor,
|
Conductor,
|
||||||
@ -44,5 +46,6 @@ __all__ = (
|
|||||||
Plugin,
|
Plugin,
|
||||||
InjectionPlugin,
|
InjectionPlugin,
|
||||||
ExposedService,
|
ExposedService,
|
||||||
Port
|
Port,
|
||||||
|
Fleet
|
||||||
)
|
)
|
||||||
|
189
iotronic/objects/fleet.py
Normal file
189
iotronic/objects/fleet.py
Normal file
@ -0,0 +1,189 @@
|
|||||||
|
# coding=utf-8
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# 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_utils import strutils
|
||||||
|
from oslo_utils import uuidutils
|
||||||
|
|
||||||
|
from iotronic.common import exception
|
||||||
|
from iotronic.db import api as db_api
|
||||||
|
from iotronic.objects import base
|
||||||
|
from iotronic.objects import utils as obj_utils
|
||||||
|
|
||||||
|
|
||||||
|
class Fleet(base.IotronicObject):
|
||||||
|
# Version 1.0: Initial version
|
||||||
|
VERSION = '1.0'
|
||||||
|
|
||||||
|
dbapi = db_api.get_instance()
|
||||||
|
|
||||||
|
fields = {
|
||||||
|
'id': int,
|
||||||
|
'uuid': obj_utils.str_or_none,
|
||||||
|
'name': obj_utils.str_or_none,
|
||||||
|
'project': obj_utils.str_or_none,
|
||||||
|
'description': obj_utils.str_or_none,
|
||||||
|
'extra': obj_utils.dict_or_none,
|
||||||
|
}
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _from_db_object(fleet, db_fleet):
|
||||||
|
"""Converts a database entity to a formal object."""
|
||||||
|
for field in fleet.fields:
|
||||||
|
fleet[field] = db_fleet[field]
|
||||||
|
fleet.obj_reset_changes()
|
||||||
|
return fleet
|
||||||
|
|
||||||
|
@base.remotable_classmethod
|
||||||
|
def get(cls, context, fleet_id):
|
||||||
|
"""Find a fleet based on its id or uuid and return a Board object.
|
||||||
|
|
||||||
|
:param fleet_id: the id *or* uuid of a fleet.
|
||||||
|
:returns: a :class:`Board` object.
|
||||||
|
"""
|
||||||
|
if strutils.is_int_like(fleet_id):
|
||||||
|
return cls.get_by_id(context, fleet_id)
|
||||||
|
elif uuidutils.is_uuid_like(fleet_id):
|
||||||
|
return cls.get_by_uuid(context, fleet_id)
|
||||||
|
else:
|
||||||
|
raise exception.InvalidIdentity(identity=fleet_id)
|
||||||
|
|
||||||
|
@base.remotable_classmethod
|
||||||
|
def get_by_id(cls, context, fleet_id):
|
||||||
|
"""Find a fleet based on its integer id and return a Board object.
|
||||||
|
|
||||||
|
:param fleet_id: the id of a fleet.
|
||||||
|
:returns: a :class:`Board` object.
|
||||||
|
"""
|
||||||
|
db_fleet = cls.dbapi.get_fleet_by_id(fleet_id)
|
||||||
|
fleet = Fleet._from_db_object(cls(context), db_fleet)
|
||||||
|
return fleet
|
||||||
|
|
||||||
|
@base.remotable_classmethod
|
||||||
|
def get_by_uuid(cls, context, uuid):
|
||||||
|
"""Find a fleet based on uuid and return a Board object.
|
||||||
|
|
||||||
|
:param uuid: the uuid of a fleet.
|
||||||
|
:returns: a :class:`Board` object.
|
||||||
|
"""
|
||||||
|
db_fleet = cls.dbapi.get_fleet_by_uuid(uuid)
|
||||||
|
fleet = Fleet._from_db_object(cls(context), db_fleet)
|
||||||
|
return fleet
|
||||||
|
|
||||||
|
@base.remotable_classmethod
|
||||||
|
def get_by_name(cls, context, name):
|
||||||
|
"""Find a fleet based on name and return a Board object.
|
||||||
|
|
||||||
|
:param name: the logical name of a fleet.
|
||||||
|
:returns: a :class:`Board` object.
|
||||||
|
"""
|
||||||
|
db_fleet = cls.dbapi.get_fleet_by_name(name)
|
||||||
|
fleet = Fleet._from_db_object(cls(context), db_fleet)
|
||||||
|
return fleet
|
||||||
|
|
||||||
|
@base.remotable_classmethod
|
||||||
|
def list(cls, context, limit=None, marker=None, sort_key=None,
|
||||||
|
sort_dir=None, filters=None):
|
||||||
|
"""Return a list of Fleet 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 to apply.
|
||||||
|
:returns: a list of :class:`Fleet` object.
|
||||||
|
|
||||||
|
"""
|
||||||
|
db_fleets = cls.dbapi.get_fleet_list(filters=filters,
|
||||||
|
limit=limit,
|
||||||
|
marker=marker,
|
||||||
|
sort_key=sort_key,
|
||||||
|
sort_dir=sort_dir)
|
||||||
|
return [Fleet._from_db_object(cls(context), obj)
|
||||||
|
for obj in db_fleets]
|
||||||
|
|
||||||
|
@base.remotable
|
||||||
|
def create(self, context=None):
|
||||||
|
"""Create a Fleet record in the DB.
|
||||||
|
|
||||||
|
Column-wise updates will be made based on the result of
|
||||||
|
self.what_changed(). If target_power_state is provided,
|
||||||
|
it will be checked against the in-database copy of the
|
||||||
|
fleet before updates are made.
|
||||||
|
|
||||||
|
: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.: Fleet(context)
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
values = self.obj_get_changes()
|
||||||
|
db_fleet = self.dbapi.create_fleet(values)
|
||||||
|
self._from_db_object(self, db_fleet)
|
||||||
|
|
||||||
|
@base.remotable
|
||||||
|
def destroy(self, context=None):
|
||||||
|
"""Delete the Fleet 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.: Fleet(context)
|
||||||
|
"""
|
||||||
|
self.dbapi.destroy_fleet(self.uuid)
|
||||||
|
self.obj_reset_changes()
|
||||||
|
|
||||||
|
@base.remotable
|
||||||
|
def save(self, context=None):
|
||||||
|
"""Save updates to this Fleet.
|
||||||
|
|
||||||
|
Column-wise updates will be made based on the result of
|
||||||
|
self.what_changed(). If target_power_state is provided,
|
||||||
|
it will be checked against the in-database copy of the
|
||||||
|
fleet before updates are made.
|
||||||
|
|
||||||
|
: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.: Fleet(context)
|
||||||
|
"""
|
||||||
|
updates = self.obj_get_changes()
|
||||||
|
self.dbapi.update_fleet(self.uuid, updates)
|
||||||
|
self.obj_reset_changes()
|
||||||
|
|
||||||
|
@base.remotable
|
||||||
|
def refresh(self, context=None):
|
||||||
|
"""Refresh the object by re-fetching 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.: Fleet(context)
|
||||||
|
"""
|
||||||
|
current = self.__class__.get_by_uuid(self._context, self.uuid)
|
||||||
|
for field in self.fields:
|
||||||
|
if (hasattr(
|
||||||
|
self, base.get_attrname(field))
|
||||||
|
and self[field] != current[field]):
|
||||||
|
self[field] = current[field]
|
Loading…
Reference in New Issue
Block a user