CRUD actions for Fleets
for Sebba :)

Change-Id: Ia6ee145f330a844e532473784e21bc375190a707
This commit is contained in:
Fabio Verboso 2018-09-20 13:09:16 +02:00
parent 094eae7851
commit 29f9f1ffd4
13 changed files with 875 additions and 6 deletions

View File

@ -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):

View 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)

View File

@ -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.

View File

@ -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.")

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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
"""

View File

@ -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')
)

View File

@ -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

View File

@ -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)

View File

@ -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
View 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]