Merge "Add resource properties discovery API"
This commit is contained in:
commit
e1c16e6b55
|
@ -128,6 +128,7 @@ Request
|
|||
.. rest_parameters:: parameters.yaml
|
||||
|
||||
- host_id: host_id_path
|
||||
- private: property_private
|
||||
|
||||
Response
|
||||
--------
|
||||
|
@ -328,3 +329,90 @@ Response
|
|||
|
||||
.. literalinclude:: ../../../doc/api_samples/hosts/allocation-get-resp.json
|
||||
:language: javascript
|
||||
|
||||
List Resource Properties
|
||||
========================
|
||||
|
||||
.. rest_method:: GET v1/os-hosts/properties
|
||||
|
||||
Get all resource properties from host
|
||||
|
||||
**Response codes**
|
||||
|
||||
Normal response code: 200
|
||||
|
||||
Error response codes: Bad Request(400), Unauthorized(401), Forbidden(403),
|
||||
Internal Server Error(500)
|
||||
|
||||
Request
|
||||
-------
|
||||
|
||||
.. rest_parameters:: parameters.yaml
|
||||
|
||||
- detail: resource_property_detail
|
||||
- all: resource_property_all
|
||||
|
||||
Response
|
||||
--------
|
||||
|
||||
.. rest_parameters:: parameters.yaml
|
||||
|
||||
- resource_properties: resource_properties
|
||||
- property: resource_properties_property
|
||||
- private: resource_properties_private
|
||||
- values: resource_properties_values
|
||||
|
||||
**Example of List Resource Properties Response**
|
||||
|
||||
.. literalinclude:: ../../../doc/api_samples/hosts/host-property-list.json
|
||||
:language: javascript
|
||||
|
||||
**Example of List Resource Properties With Detail Response**
|
||||
|
||||
.. literalinclude:: ../../../doc/api_samples/hosts/host-property-list-detail.json
|
||||
:language: javascript
|
||||
|
||||
Update Resource Properties
|
||||
==========================
|
||||
|
||||
.. rest_method:: PATCH v1/os-hosts/properties/{property_name}
|
||||
|
||||
Update a host resource properties
|
||||
|
||||
**Response codes**
|
||||
|
||||
Normal response code: 200
|
||||
|
||||
Error response codes: Bad Request(400), Unauthorized(401), Forbidden(403),
|
||||
Internal Server Error(500)
|
||||
|
||||
Request
|
||||
-------
|
||||
|
||||
.. rest_parameters:: parameters.yaml
|
||||
|
||||
- property_name: property_name
|
||||
- private: property_private
|
||||
|
||||
**Example of Update Resource Properties**
|
||||
|
||||
.. literalinclude:: ../../../doc/api_samples/hosts/host-property-update.json
|
||||
:language: javascript
|
||||
|
||||
Response
|
||||
--------
|
||||
|
||||
.. rest_parameters:: parameters.yaml
|
||||
|
||||
- created_at: created_at
|
||||
- updated_at: updated_at
|
||||
- id: resource_property_id
|
||||
- resource_type: resource_property_resource_type
|
||||
- property_name: resource_properties_property
|
||||
- private: resource_property_private
|
||||
|
||||
**Example of List Resource Properties Response**
|
||||
|
||||
.. literalinclude:: ../../../doc/api_samples/hosts/host-property-update-res.json
|
||||
:language: javascript
|
||||
|
||||
|
|
|
@ -42,6 +42,12 @@ lease_id_path:
|
|||
in: path
|
||||
required: true
|
||||
type: string
|
||||
property_name:
|
||||
description: |
|
||||
The name of the property.
|
||||
in: path
|
||||
required: true
|
||||
type: string
|
||||
|
||||
|
||||
# variables in query
|
||||
|
@ -57,6 +63,19 @@ allocation_reservation_id_query:
|
|||
in: query
|
||||
required: false
|
||||
type: string
|
||||
resource_property_all:
|
||||
description: |
|
||||
Whether to include all resource properties, public and private.
|
||||
in: query
|
||||
required: false
|
||||
type: string
|
||||
resource_property_detail:
|
||||
description: |
|
||||
Whether to include values along for each property and if the property
|
||||
is private.
|
||||
in: query
|
||||
required: false
|
||||
type: string
|
||||
|
||||
|
||||
# variables in body
|
||||
|
@ -406,6 +425,13 @@ leases:
|
|||
in: body
|
||||
required: true
|
||||
type: array
|
||||
property_private:
|
||||
description: |
|
||||
Whether the property is private.
|
||||
|
||||
in: body
|
||||
required: true
|
||||
type: boolean
|
||||
reservation:
|
||||
description: |
|
||||
A ``reservation`` object.
|
||||
|
@ -627,6 +653,69 @@ reservations_optional:
|
|||
in: body
|
||||
required: false
|
||||
type: array
|
||||
resource_properties:
|
||||
description: |
|
||||
A list of ``resource_property`` objects.
|
||||
|
||||
in: body
|
||||
required: true
|
||||
type: array
|
||||
resource_properties_private:
|
||||
description: |
|
||||
Whether the property is private.
|
||||
|
||||
in: body
|
||||
required: false
|
||||
type: boolean
|
||||
resource_properties_property:
|
||||
description: |
|
||||
The name of the property.
|
||||
|
||||
in: body
|
||||
required: true
|
||||
type: any
|
||||
resource_properties_values:
|
||||
description: |
|
||||
A list of values for the property.
|
||||
|
||||
in: body
|
||||
required: false
|
||||
type: array
|
||||
resource_property:
|
||||
description: |
|
||||
The updated ``resource_property`` object.
|
||||
|
||||
in: body
|
||||
required: true
|
||||
type: any
|
||||
resource_property_id:
|
||||
description: |
|
||||
The updated ``resource_property`` UUID.
|
||||
|
||||
in: body
|
||||
required: true
|
||||
type: string
|
||||
resource_property_private:
|
||||
description: |
|
||||
Whether the updated ``resource_property`` is private.
|
||||
|
||||
in: body
|
||||
required: true
|
||||
type: boolean
|
||||
resource_property_property_name:
|
||||
description: |
|
||||
The updated ``resource_property`` property_name.
|
||||
|
||||
in: body
|
||||
required: true
|
||||
type: string
|
||||
resource_property_resource_type:
|
||||
description: |
|
||||
The updated ``resource_property`` resource type.
|
||||
|
||||
in: body
|
||||
required: true
|
||||
type: string
|
||||
updated_at:
|
||||
description: |
|
||||
The date and time when the object was updated.
|
||||
|
|
|
@ -86,3 +86,14 @@ class API(object):
|
|||
:type query: dict
|
||||
"""
|
||||
return self.manager_rpcapi.get_allocations(host_id, query)
|
||||
|
||||
@policy.authorize('oshosts', 'get_resource_properties')
|
||||
def list_resource_properties(self, query):
|
||||
"""List resource properties for hosts."""
|
||||
return self.manager_rpcapi.list_resource_properties(query)
|
||||
|
||||
@policy.authorize('oshosts', 'update_resource_properties')
|
||||
def update_resource_property(self, property_name, data):
|
||||
"""Update a host resource property."""
|
||||
return self.manager_rpcapi.update_resource_property(
|
||||
property_name, data)
|
||||
|
|
|
@ -79,3 +79,17 @@ def allocations_list(req, query):
|
|||
def allocations_get(req, host_id, query):
|
||||
"""List all allocations on a specific host."""
|
||||
return api_utils.render(allocation=_api.get_allocations(host_id, query))
|
||||
|
||||
|
||||
@rest.get('/properties', query=True)
|
||||
def resource_properties_list(req, query=None):
|
||||
"""List computehost resource properties."""
|
||||
return api_utils.render(
|
||||
resource_properties=_api.list_resource_properties(query))
|
||||
|
||||
|
||||
@rest.patch('/properties/<property_name>')
|
||||
def resource_property_update(req, property_name, data):
|
||||
"""Update a computehost resource property."""
|
||||
return api_utils.render(
|
||||
resource_property=_api.update_resource_property(property_name, data))
|
||||
|
|
|
@ -53,6 +53,9 @@ class Rest(flask.Blueprint):
|
|||
def put(self, rule, status_code=200):
|
||||
return self._mroute('PUT', rule, status_code)
|
||||
|
||||
def patch(self, rule, status_code=200):
|
||||
return self._mroute('PATCH', rule, status_code)
|
||||
|
||||
def delete(self, rule, status_code=204):
|
||||
return self._mroute('DELETE', rule, status_code)
|
||||
|
||||
|
@ -79,7 +82,7 @@ class Rest(flask.Blueprint):
|
|||
if status:
|
||||
flask.request.status_code = status
|
||||
|
||||
if flask.request.method in ['POST', 'PUT']:
|
||||
if flask.request.method in ['POST', 'PUT', 'PATCH']:
|
||||
kwargs['data'] = request_data()
|
||||
|
||||
if flask.request.endpoint in self.routes_with_query_support:
|
||||
|
|
|
@ -385,13 +385,11 @@ def host_extra_capability_create(values):
|
|||
return IMPL.host_extra_capability_create(values)
|
||||
|
||||
|
||||
@to_dict
|
||||
def host_extra_capability_get(host_extra_capability_id):
|
||||
"""Return a specific Host Extracapability."""
|
||||
return IMPL.host_extra_capability_get(host_extra_capability_id)
|
||||
|
||||
|
||||
@to_dict
|
||||
def host_extra_capability_get_all_per_host(host_id):
|
||||
"""Return all extra_capabilities belonging to a specific Compute host."""
|
||||
return IMPL.host_extra_capability_get_all_per_host(host_id)
|
||||
|
@ -410,7 +408,6 @@ def host_extra_capability_update(host_extra_capability_id, values):
|
|||
def host_extra_capability_get_all_per_name(host_id,
|
||||
extra_capability_name):
|
||||
return IMPL.host_extra_capability_get_all_per_name(host_id,
|
||||
|
||||
extra_capability_name)
|
||||
|
||||
|
||||
|
@ -525,3 +522,17 @@ def reservable_fip_get_all_by_queries(queries):
|
|||
def floatingip_destroy(floatingip_id):
|
||||
"""Delete specific floating ip."""
|
||||
IMPL.floatingip_destroy(floatingip_id)
|
||||
|
||||
|
||||
# Resource Properties
|
||||
|
||||
def resource_properties_list(resource_type):
|
||||
return IMPL.resource_properties_list(resource_type)
|
||||
|
||||
|
||||
def resource_property_update(resource_type, property_name, values):
|
||||
return IMPL.resource_property_update(resource_type, property_name, values)
|
||||
|
||||
|
||||
def resource_property_create(values):
|
||||
return IMPL.resource_property_create(values)
|
||||
|
|
|
@ -40,3 +40,12 @@ class BlazarDBInvalidFilter(BlazarDBException):
|
|||
|
||||
class BlazarDBInvalidFilterOperator(BlazarDBException):
|
||||
msg_fmt = _('%(filter_operator)s is invalid')
|
||||
|
||||
|
||||
class BlazarDBResourcePropertiesNotEnabled(BlazarDBException):
|
||||
msq_fmt = _('%(resource_type)s does not have resource properties enabled.')
|
||||
|
||||
|
||||
class BlazarDBInvalidResourceProperty(BlazarDBException):
|
||||
msg_fmt = _('%(property_name)s does not exist for resource type '
|
||||
'%(resource_type)s.')
|
||||
|
|
|
@ -0,0 +1,101 @@
|
|||
# Copyright 2022 OpenStack Foundation.
|
||||
#
|
||||
# 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.
|
||||
|
||||
"""resource property
|
||||
|
||||
Revision ID: 02e2f2186d98
|
||||
Revises: f4084140f608
|
||||
Create Date: 2020-04-17 15:51:40.542459
|
||||
|
||||
"""
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '02e2f2186d98'
|
||||
down_revision = 'f4084140f608'
|
||||
|
||||
import uuid
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy.dialects import mysql
|
||||
|
||||
|
||||
def upgrade():
|
||||
op.create_table('resource_properties',
|
||||
sa.Column('id', sa.String(length=36), nullable=False),
|
||||
sa.Column('resource_type', sa.String(255), nullable=False),
|
||||
sa.Column('property_name', sa.String(255),
|
||||
nullable=False),
|
||||
sa.Column('private', sa.Boolean, nullable=False,
|
||||
server_default=sa.false()),
|
||||
sa.Column('created_at', sa.DateTime(), nullable=True),
|
||||
sa.Column('updated_at', sa.DateTime(), nullable=True),
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
sa.UniqueConstraint('resource_type', 'property_name'))
|
||||
|
||||
if op.get_bind().engine.name != 'sqlite':
|
||||
connection = op.get_bind()
|
||||
|
||||
host_query = connection.execute("""
|
||||
SELECT DISTINCT "physical:host", capability_name
|
||||
FROM computehost_extra_capabilities;""")
|
||||
|
||||
capability_values = [
|
||||
(str(uuid.uuid4()), resource_type, capability_name)
|
||||
for resource_type, capability_name
|
||||
in host_query.fetchall()]
|
||||
|
||||
if capability_values:
|
||||
insert = """
|
||||
INSERT INTO resource_properties
|
||||
(id, resource_type, property_name)
|
||||
VALUES {};"""
|
||||
connection.execute(
|
||||
insert.format(', '.join(map(str, capability_values))))
|
||||
|
||||
op.add_column('computehost_extra_capabilities',
|
||||
sa.Column('property_id', sa.String(length=255),
|
||||
nullable=False))
|
||||
|
||||
connection.execute("""
|
||||
UPDATE computehost_extra_capabilities c
|
||||
LEFT JOIN resource_properties e
|
||||
ON e.property_name = c.capability_name
|
||||
SET c.property_id = e.id;""")
|
||||
|
||||
op.create_foreign_key('computehost_resource_property_id_fk',
|
||||
'computehost_extra_capabilities',
|
||||
'resource_properties', ['property_id'], ['id'])
|
||||
op.drop_column('computehost_extra_capabilities', 'capability_name')
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.add_column('computehost_extra_capabilities',
|
||||
sa.Column('capability_name', mysql.VARCHAR(length=64),
|
||||
nullable=False))
|
||||
|
||||
if op.get_bind().engine.name != 'sqlite':
|
||||
connection = op.get_bind()
|
||||
connection.execute("""
|
||||
UPDATE computehost_extra_capabilities c
|
||||
LEFT JOIN resource_properties e
|
||||
ON e.id=c.property_id
|
||||
SET c.capability_name = e.property_name;""")
|
||||
op.drop_constraint('computehost_resource_property_id_fk',
|
||||
'computehost_extra_capabilities',
|
||||
type_='foreignkey')
|
||||
op.drop_column('computehost_extra_capabilities', 'property_id')
|
||||
op.drop_table('resource_properties')
|
|
@ -29,6 +29,9 @@ from blazar.db import exceptions as db_exc
|
|||
from blazar.db.sqlalchemy import facade_wrapper
|
||||
from blazar.db.sqlalchemy import models
|
||||
|
||||
RESOURCE_PROPERTY_MODELS = {
|
||||
'physical:host': models.ComputeHostExtraCapability,
|
||||
}
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
@ -663,26 +666,28 @@ def host_get_all_by_queries(queries):
|
|||
|
||||
hosts_query = hosts_query.filter(filt)
|
||||
else:
|
||||
# looking for extra capabilities matches
|
||||
extra_filter = model_query(
|
||||
models.ComputeHostExtraCapability, get_session()
|
||||
).filter(models.ComputeHostExtraCapability.capability_name == key
|
||||
).all()
|
||||
# looking for resource properties matches
|
||||
extra_filter = (
|
||||
_host_resource_property_query(get_session())
|
||||
.filter(models.ResourceProperty.property_name == key)
|
||||
).all()
|
||||
|
||||
if not extra_filter:
|
||||
raise db_exc.BlazarDBNotFound(
|
||||
id=key, model='ComputeHostExtraCapability')
|
||||
|
||||
for host in extra_filter:
|
||||
for host, property_name in extra_filter:
|
||||
print(dir(host))
|
||||
if op in oper and oper[op][1](host.capability_value, value):
|
||||
hosts.append(host.computehost_id)
|
||||
elif op not in oper:
|
||||
msg = 'Operator %s for extra capabilities not implemented'
|
||||
msg = 'Operator %s for resource properties not implemented'
|
||||
raise NotImplementedError(msg % op)
|
||||
|
||||
# We must also avoid selecting any host which doesn't have the
|
||||
# extra capability present.
|
||||
all_hosts = [h.id for h in hosts_query.all()]
|
||||
extra_filter_hosts = [h.computehost_id for h in extra_filter]
|
||||
extra_filter_hosts = [h.computehost_id for h, _ in extra_filter]
|
||||
hosts += [h for h in all_hosts if h not in extra_filter_hosts]
|
||||
|
||||
return hosts_query.filter(~models.ComputeHost.id.in_(hosts)).all()
|
||||
|
@ -755,9 +760,19 @@ def host_destroy(host_id):
|
|||
|
||||
|
||||
# ComputeHostExtraCapability
|
||||
|
||||
def _host_resource_property_query(session):
|
||||
return (
|
||||
model_query(models.ComputeHostExtraCapability, session)
|
||||
.join(models.ResourceProperty)
|
||||
.add_column(models.ResourceProperty.property_name))
|
||||
|
||||
|
||||
def _host_extra_capability_get(session, host_extra_capability_id):
|
||||
query = model_query(models.ComputeHostExtraCapability, session)
|
||||
return query.filter_by(id=host_extra_capability_id).first()
|
||||
query = _host_resource_property_query(session).filter(
|
||||
models.ComputeHostExtraCapability.id == host_extra_capability_id)
|
||||
|
||||
return query.first()
|
||||
|
||||
|
||||
def host_extra_capability_get(host_extra_capability_id):
|
||||
|
@ -766,8 +781,10 @@ def host_extra_capability_get(host_extra_capability_id):
|
|||
|
||||
|
||||
def _host_extra_capability_get_all_per_host(session, host_id):
|
||||
query = model_query(models.ComputeHostExtraCapability, session)
|
||||
return query.filter_by(computehost_id=host_id)
|
||||
query = _host_resource_property_query(session).filter(
|
||||
models.ComputeHostExtraCapability.computehost_id == host_id)
|
||||
|
||||
return query
|
||||
|
||||
|
||||
def host_extra_capability_get_all_per_host(host_id):
|
||||
|
@ -777,6 +794,13 @@ def host_extra_capability_get_all_per_host(host_id):
|
|||
|
||||
def host_extra_capability_create(values):
|
||||
values = values.copy()
|
||||
|
||||
resource_property = resource_property_get_or_create(
|
||||
'physical:host', values.get('property_name'))
|
||||
|
||||
del values['property_name']
|
||||
values['property_id'] = resource_property.id
|
||||
|
||||
host_extra_capability = models.ComputeHostExtraCapability()
|
||||
host_extra_capability.update(values)
|
||||
|
||||
|
@ -797,7 +821,7 @@ def host_extra_capability_update(host_extra_capability_id, values):
|
|||
session = get_session()
|
||||
|
||||
with session.begin():
|
||||
host_extra_capability = (
|
||||
host_extra_capability, _ = (
|
||||
_host_extra_capability_get(session,
|
||||
host_extra_capability_id))
|
||||
host_extra_capability.update(values)
|
||||
|
@ -809,9 +833,8 @@ def host_extra_capability_update(host_extra_capability_id, values):
|
|||
def host_extra_capability_destroy(host_extra_capability_id):
|
||||
session = get_session()
|
||||
with session.begin():
|
||||
host_extra_capability = (
|
||||
_host_extra_capability_get(session,
|
||||
host_extra_capability_id))
|
||||
host_extra_capability = _host_extra_capability_get(
|
||||
session, host_extra_capability_id)
|
||||
|
||||
if not host_extra_capability:
|
||||
# raise not found error
|
||||
|
@ -819,15 +842,16 @@ def host_extra_capability_destroy(host_extra_capability_id):
|
|||
id=host_extra_capability_id,
|
||||
model='ComputeHostExtraCapability')
|
||||
|
||||
session.delete(host_extra_capability)
|
||||
session.delete(host_extra_capability[0])
|
||||
|
||||
|
||||
def host_extra_capability_get_all_per_name(host_id, capability_name):
|
||||
def host_extra_capability_get_all_per_name(host_id, property_name):
|
||||
session = get_session()
|
||||
|
||||
with session.begin():
|
||||
query = _host_extra_capability_get_all_per_host(session, host_id)
|
||||
return query.filter_by(capability_name=capability_name).all()
|
||||
return query.filter(
|
||||
models.ResourceProperty.property_name == property_name).all()
|
||||
|
||||
|
||||
# FloatingIP reservation
|
||||
|
@ -1115,3 +1139,101 @@ def floatingip_destroy(floatingip_id):
|
|||
raise db_exc.BlazarDBNotFound(id=floatingip_id, model='FloatingIP')
|
||||
|
||||
session.delete(floatingip)
|
||||
|
||||
|
||||
# Resource Properties
|
||||
|
||||
def _resource_property_get(session, resource_type, property_name):
|
||||
query = (
|
||||
model_query(models.ResourceProperty, session)
|
||||
.filter_by(resource_type=resource_type)
|
||||
.filter_by(property_name=property_name))
|
||||
|
||||
return query.first()
|
||||
|
||||
|
||||
def resource_property_get(resource_type, property_name):
|
||||
return _resource_property_get(get_session(), resource_type, property_name)
|
||||
|
||||
|
||||
def resource_properties_list(resource_type):
|
||||
if resource_type not in RESOURCE_PROPERTY_MODELS:
|
||||
raise db_exc.BlazarDBResourcePropertiesNotEnabled(
|
||||
resource_type=resource_type)
|
||||
|
||||
session = get_session()
|
||||
|
||||
with session.begin():
|
||||
|
||||
resource_model = RESOURCE_PROPERTY_MODELS[resource_type]
|
||||
query = session.query(
|
||||
models.ResourceProperty.property_name,
|
||||
models.ResourceProperty.private,
|
||||
resource_model.capability_value).join(resource_model).distinct()
|
||||
|
||||
return query.all()
|
||||
|
||||
|
||||
def _resource_property_create(session, values):
|
||||
values = values.copy()
|
||||
|
||||
resource_property = models.ResourceProperty()
|
||||
resource_property.update(values)
|
||||
|
||||
with session.begin():
|
||||
try:
|
||||
resource_property.save(session=session)
|
||||
except common_db_exc.DBDuplicateEntry as e:
|
||||
# raise exception about duplicated columns (e.columns)
|
||||
raise db_exc.BlazarDBDuplicateEntry(
|
||||
model=resource_property.__class__.__name__,
|
||||
columns=e.columns)
|
||||
|
||||
return resource_property_get(values.get('resource_type'),
|
||||
values.get('property_name'))
|
||||
|
||||
|
||||
def resource_property_create(values):
|
||||
return _resource_property_create(get_session(), values)
|
||||
|
||||
|
||||
def resource_property_update(resource_type, property_name, values):
|
||||
if resource_type not in RESOURCE_PROPERTY_MODELS:
|
||||
raise db_exc.BlazarDBResourcePropertiesNotEnabled(
|
||||
resource_type=resource_type)
|
||||
|
||||
values = values.copy()
|
||||
session = get_session()
|
||||
|
||||
with session.begin():
|
||||
resource_property = _resource_property_get(
|
||||
session, resource_type, property_name)
|
||||
|
||||
if not resource_property:
|
||||
raise db_exc.BlazarDBInvalidResourceProperty(
|
||||
property_name=property_name,
|
||||
resource_type=resource_type)
|
||||
|
||||
resource_property.update(values)
|
||||
resource_property.save(session=session)
|
||||
|
||||
return resource_property_get(resource_type, property_name)
|
||||
|
||||
|
||||
def _resource_property_get_or_create(session, resource_type, property_name):
|
||||
resource_property = _resource_property_get(
|
||||
session, resource_type, property_name)
|
||||
|
||||
if resource_property:
|
||||
return resource_property
|
||||
else:
|
||||
rp_values = {
|
||||
'resource_type': resource_type,
|
||||
'property_name': property_name}
|
||||
|
||||
return resource_property_create(rp_values)
|
||||
|
||||
|
||||
def resource_property_get_or_create(resource_type, property_name):
|
||||
return _resource_property_get_or_create(
|
||||
get_session(), resource_type, property_name)
|
||||
|
|
|
@ -155,6 +155,23 @@ class Event(mb.BlazarBase):
|
|||
return super(Event, self).to_dict()
|
||||
|
||||
|
||||
class ResourceProperty(mb.BlazarBase):
|
||||
"""Defines an resource property by resource type."""
|
||||
|
||||
__tablename__ = 'resource_properties'
|
||||
|
||||
id = _id_column()
|
||||
resource_type = sa.Column(sa.String(255), nullable=False)
|
||||
property_name = sa.Column(sa.String(255), nullable=False)
|
||||
private = sa.Column(sa.Boolean, nullable=False,
|
||||
server_default=sa.false())
|
||||
|
||||
__table_args__ = (sa.UniqueConstraint('resource_type', 'property_name'),)
|
||||
|
||||
def to_dict(self):
|
||||
return super(ResourceProperty, self).to_dict()
|
||||
|
||||
|
||||
class ComputeHostReservation(mb.BlazarBase):
|
||||
"""Description
|
||||
|
||||
|
@ -252,7 +269,9 @@ class ComputeHostExtraCapability(mb.BlazarBase):
|
|||
|
||||
id = _id_column()
|
||||
computehost_id = sa.Column(sa.String(36), sa.ForeignKey('computehosts.id'))
|
||||
capability_name = sa.Column(sa.String(64), nullable=False)
|
||||
property_id = sa.Column(sa.String(36),
|
||||
sa.ForeignKey('resource_properties.id'),
|
||||
nullable=False)
|
||||
capability_value = sa.Column(MediumText(), nullable=False)
|
||||
|
||||
def to_dict(self):
|
||||
|
|
|
@ -64,3 +64,12 @@ class ManagerRPCAPI(service.RPCClient):
|
|||
"""List all allocations on a specified computehost."""
|
||||
return self.call('physical:host:get_allocations',
|
||||
host_id=host_id, query=query)
|
||||
|
||||
def list_resource_properties(self, query):
|
||||
"""List resource properties and possible values for computehosts."""
|
||||
return self.call('physical:host:list_resource_properties', query=query)
|
||||
|
||||
def update_resource_property(self, property_name, values):
|
||||
"""Update resource property for computehost."""
|
||||
return self.call('physical:host:update_resource_property',
|
||||
property_name=property_name, values=values)
|
||||
|
|
|
@ -14,7 +14,11 @@
|
|||
# limitations under the License.
|
||||
|
||||
import abc
|
||||
import collections
|
||||
|
||||
from blazar import context
|
||||
from blazar.db import api as db_api
|
||||
from blazar import policy
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log as logging
|
||||
|
||||
|
@ -98,6 +102,31 @@ class BasePlugin(object, metaclass=abc.ABCMeta):
|
|||
"""Wake up resource."""
|
||||
pass
|
||||
|
||||
def list_resource_properties(self, query):
|
||||
detail = False if not query else query.get('detail', False)
|
||||
all_properties = False if not query else query.get('all', False)
|
||||
resource_properties = collections.defaultdict(list)
|
||||
|
||||
include_private = all_properties and policy.enforce(
|
||||
context.current(), 'admin', {}, do_raise=False)
|
||||
|
||||
for name, private, value in db_api.resource_properties_list(
|
||||
self.resource_type):
|
||||
|
||||
if include_private or not private:
|
||||
resource_properties[name].append(value)
|
||||
|
||||
if detail:
|
||||
return [
|
||||
dict(property=k, private=False, values=v)
|
||||
for k, v in resource_properties.items()]
|
||||
else:
|
||||
return [dict(property=k) for k, v in resource_properties.items()]
|
||||
|
||||
def update_resource_property(self, property_name, values):
|
||||
return db_api.resource_property_update(
|
||||
self.resource_type, property_name, values)
|
||||
|
||||
def before_end(self, resource_id):
|
||||
"""Take actions before the end of a lease"""
|
||||
pass
|
||||
|
|
|
@ -809,9 +809,9 @@ class VirtualInstancePlugin(base.BasePlugin, nova.NovaClientWrapper):
|
|||
extra_capabilities = {}
|
||||
raw_extra_capabilities = (
|
||||
db_api.host_extra_capability_get_all_per_host(host_id))
|
||||
for capability in raw_extra_capabilities:
|
||||
key = capability['capability_name']
|
||||
extra_capabilities[key] = capability['capability_value']
|
||||
for capability, capability_name in raw_extra_capabilities:
|
||||
key = capability_name
|
||||
extra_capabilities[key] = capability.capability_value
|
||||
return extra_capabilities
|
||||
|
||||
def get(self, host_id):
|
||||
|
|
|
@ -302,9 +302,9 @@ class PhysicalHostPlugin(base.BasePlugin, nova.NovaClientWrapper):
|
|||
extra_capabilities = {}
|
||||
raw_extra_capabilities = (
|
||||
db_api.host_extra_capability_get_all_per_host(host_id))
|
||||
for capability in raw_extra_capabilities:
|
||||
key = capability['capability_name']
|
||||
extra_capabilities[key] = capability['capability_value']
|
||||
for capability, property_name in raw_extra_capabilities:
|
||||
key = property_name
|
||||
extra_capabilities[key] = capability.capability_value
|
||||
return extra_capabilities
|
||||
|
||||
def get(self, host_id):
|
||||
|
@ -383,7 +383,7 @@ class PhysicalHostPlugin(base.BasePlugin, nova.NovaClientWrapper):
|
|||
raise e
|
||||
for key in extra_capabilities:
|
||||
values = {'computehost_id': host['id'],
|
||||
'capability_name': key,
|
||||
'property_name': key,
|
||||
'capability_value': extra_capabilities[key],
|
||||
}
|
||||
try:
|
||||
|
@ -396,7 +396,7 @@ class PhysicalHostPlugin(base.BasePlugin, nova.NovaClientWrapper):
|
|||
host=host['id'])
|
||||
return self.get_computehost(host['id'])
|
||||
|
||||
def is_updatable_extra_capability(self, capability):
|
||||
def is_updatable_extra_capability(self, capability, property_name):
|
||||
reservations = db_utils.get_reservations_by_host_id(
|
||||
capability['computehost_id'], datetime.datetime.utcnow(),
|
||||
datetime.date.max)
|
||||
|
@ -413,7 +413,7 @@ class PhysicalHostPlugin(base.BasePlugin, nova.NovaClientWrapper):
|
|||
# the extra_capability.
|
||||
for requirement in requirements_queries:
|
||||
# A requirement is of the form "key op value" as string
|
||||
if requirement.split(" ")[0] == capability['capability_name']:
|
||||
if requirement.split(" ")[0] == property_name:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
@ -428,37 +428,33 @@ class PhysicalHostPlugin(base.BasePlugin, nova.NovaClientWrapper):
|
|||
new_keys = set(values.keys()) - set(previous_capabilities.keys())
|
||||
|
||||
for key in updated_keys:
|
||||
raw_capability = next(iter(
|
||||
raw_capability, property_name = next(iter(
|
||||
db_api.host_extra_capability_get_all_per_name(host_id, key)))
|
||||
capability = {
|
||||
'capability_name': key,
|
||||
'capability_value': values[key],
|
||||
}
|
||||
if self.is_updatable_extra_capability(raw_capability):
|
||||
capability = {'capability_value': values[key]}
|
||||
|
||||
if self.is_updatable_extra_capability(
|
||||
raw_capability, property_name):
|
||||
try:
|
||||
db_api.host_extra_capability_update(
|
||||
raw_capability['id'], capability)
|
||||
except (db_ex.BlazarDBException, RuntimeError):
|
||||
cant_update_extra_capability.append(
|
||||
raw_capability['capability_name'])
|
||||
cant_update_extra_capability.append(property_name)
|
||||
else:
|
||||
LOG.info("Capability %s can't be updated because "
|
||||
"existing reservations require it.",
|
||||
raw_capability['capability_name'])
|
||||
cant_update_extra_capability.append(
|
||||
raw_capability['capability_name'])
|
||||
property_name)
|
||||
cant_update_extra_capability.append(property_name)
|
||||
|
||||
for key in new_keys:
|
||||
new_capability = {
|
||||
'computehost_id': host_id,
|
||||
'capability_name': key,
|
||||
'property_name': key,
|
||||
'capability_value': values[key],
|
||||
}
|
||||
try:
|
||||
db_api.host_extra_capability_create(new_capability)
|
||||
except (db_ex.BlazarDBException, RuntimeError):
|
||||
cant_update_extra_capability.append(
|
||||
new_capability['capability_name'])
|
||||
cant_update_extra_capability.append(key)
|
||||
|
||||
if cant_update_extra_capability:
|
||||
raise manager_ex.CantAddExtraCapability(
|
||||
|
|
|
@ -79,7 +79,30 @@ oshosts_policies = [
|
|||
'method': 'GET'
|
||||
}
|
||||
]
|
||||
)
|
||||
),
|
||||
policy.DocumentedRuleDefault(
|
||||
name=POLICY_ROOT % 'get_resource_properties',
|
||||
check_str=base.RULE_ADMIN,
|
||||
description='Policy rule for Resource Properties API.',
|
||||
operations=[
|
||||
{
|
||||
'path': '/{api_version}/os-hosts/resource_properties',
|
||||
'method': 'GET'
|
||||
}
|
||||
]
|
||||
),
|
||||
policy.DocumentedRuleDefault(
|
||||
name=POLICY_ROOT % 'update_resource_properties',
|
||||
check_str=base.RULE_ADMIN,
|
||||
description='Policy rule for Resource Properties API.',
|
||||
operations=[
|
||||
{
|
||||
'path': ('/{api_version}/os-hosts/resource_properties/'
|
||||
'{property_name}'),
|
||||
'method': 'PATCH'
|
||||
}
|
||||
]
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
|
|
|
@ -100,6 +100,10 @@ class OsHostAPITestCase(tests.TestCase):
|
|||
self.list_allocations = self.patch(service_api.API,
|
||||
'list_allocations')
|
||||
self.get_allocations = self.patch(service_api.API, 'get_allocations')
|
||||
self.list_resource_properties = self.patch(service_api.API,
|
||||
'list_resource_properties')
|
||||
self.update_resource_property = self.patch(service_api.API,
|
||||
'update_resource_property')
|
||||
|
||||
def _assert_response(self, actual_resp, expected_status_code,
|
||||
expected_resp_body, key='host',
|
||||
|
@ -237,3 +241,20 @@ class OsHostAPITestCase(tests.TestCase):
|
|||
res = c.get('/v1/{0}/allocation?{1}'.format(
|
||||
self.host_id, query_params), headers=self.headers)
|
||||
self._assert_response(res, 200, {}, key='allocation')
|
||||
|
||||
def test_resource_properties_list(self):
|
||||
with self.app.test_client() as c:
|
||||
self.list_resource_properties.return_value = []
|
||||
res = c.get('/v1/properties', headers=self.headers)
|
||||
self._assert_response(res, 200, [], key='resource_properties')
|
||||
|
||||
def test_resource_property_update(self):
|
||||
resource_property = 'fake_property'
|
||||
resource_property_body = {'private': True}
|
||||
|
||||
with self.app.test_client() as c:
|
||||
|
||||
res = c.patch('/v1/properties/{0}'.format(resource_property),
|
||||
json=resource_property_body,
|
||||
headers=self.headers)
|
||||
self._assert_response(res, 200, {}, 'resource_property')
|
||||
|
|
|
@ -193,7 +193,7 @@ def _get_fake_host_extra_capabilities(id=None,
|
|||
computehost_id = _get_fake_random_uuid()
|
||||
return {'id': id,
|
||||
'computehost_id': computehost_id,
|
||||
'capability_name': name,
|
||||
'property_name': name,
|
||||
'capability_value': value}
|
||||
|
||||
|
||||
|
@ -507,6 +507,12 @@ class SQLAlchemyDBApiTestCase(tests.DBTestCase):
|
|||
"""Create one host and test extra capability queries."""
|
||||
# We create a first host, with extra capabilities
|
||||
db_api.host_create(_get_fake_host_values(id=1))
|
||||
db_api.resource_property_create(dict(
|
||||
id='a', resource_type='physical:host', private=False,
|
||||
property_name='vgpu'))
|
||||
db_api.resource_property_create(dict(
|
||||
id='b', resource_type='physical:host', private=False,
|
||||
property_name='nic_model'))
|
||||
db_api.host_extra_capability_create(
|
||||
_get_fake_host_extra_capabilities(computehost_id=1))
|
||||
db_api.host_extra_capability_create(_get_fake_host_extra_capabilities(
|
||||
|
@ -533,6 +539,20 @@ class SQLAlchemyDBApiTestCase(tests.DBTestCase):
|
|||
db_api.host_get_all_by_queries(['nic_model == ACME Model A'])
|
||||
))
|
||||
|
||||
def test_resource_properties_list(self):
|
||||
"""Create one host and test extra capability queries."""
|
||||
# We create a first host, with extra capabilities
|
||||
db_api.host_create(_get_fake_host_values(id=1))
|
||||
db_api.resource_property_create(dict(
|
||||
id='a', resource_type='physical:host', private=False,
|
||||
property_name='vgpu'))
|
||||
db_api.host_extra_capability_create(
|
||||
_get_fake_host_extra_capabilities(computehost_id=1))
|
||||
|
||||
result = db_api.resource_properties_list('physical:host')
|
||||
|
||||
self.assertListEqual(result, [('vgpu', False, '2')])
|
||||
|
||||
def test_search_for_hosts_by_composed_queries(self):
|
||||
"""Create one host and test composed queries."""
|
||||
|
||||
|
@ -580,9 +600,13 @@ class SQLAlchemyDBApiTestCase(tests.DBTestCase):
|
|||
db_api.host_destroy, 2)
|
||||
|
||||
def test_create_host_extra_capability(self):
|
||||
result = db_api.host_extra_capability_create(
|
||||
_get_fake_host_extra_capabilities(id=1))
|
||||
self.assertEqual(result['id'], _get_fake_host_values(id='1')['id'])
|
||||
db_api.resource_property_create(dict(
|
||||
id='id', resource_type='physical:host', private=False,
|
||||
property_name='vgpu'))
|
||||
result, _ = db_api.host_extra_capability_create(
|
||||
_get_fake_host_extra_capabilities(id=1, name='vgpu'))
|
||||
|
||||
self.assertEqual(result.id, _get_fake_host_values(id='1')['id'])
|
||||
|
||||
def test_create_duplicated_host_extra_capability(self):
|
||||
db_api.host_extra_capability_create(
|
||||
|
@ -594,8 +618,8 @@ class SQLAlchemyDBApiTestCase(tests.DBTestCase):
|
|||
def test_get_host_extra_capability_per_id(self):
|
||||
db_api.host_extra_capability_create(
|
||||
_get_fake_host_extra_capabilities(id='1'))
|
||||
result = db_api.host_extra_capability_get('1')
|
||||
self.assertEqual('1', result['id'])
|
||||
result, _ = db_api.host_extra_capability_get('1')
|
||||
self.assertEqual('1', result.id)
|
||||
|
||||
def test_host_extra_capability_get_all_per_host(self):
|
||||
db_api.host_extra_capability_create(
|
||||
|
@ -609,8 +633,8 @@ class SQLAlchemyDBApiTestCase(tests.DBTestCase):
|
|||
db_api.host_extra_capability_create(
|
||||
_get_fake_host_extra_capabilities(id='1'))
|
||||
db_api.host_extra_capability_update('1', {'capability_value': '2'})
|
||||
res = db_api.host_extra_capability_get('1')
|
||||
self.assertEqual('2', res['capability_value'])
|
||||
res, _ = db_api.host_extra_capability_get('1')
|
||||
self.assertEqual('2', res.capability_value)
|
||||
|
||||
def test_delete_host_extra_capability(self):
|
||||
db_api.host_extra_capability_create(
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import collections
|
||||
import datetime
|
||||
from unittest import mock
|
||||
|
||||
|
@ -81,18 +82,13 @@ class PhysicalHostPluginSetupOnlyTestCase(tests.TestCase):
|
|||
self.fake_phys_plugin.project_domain_name)
|
||||
|
||||
def test__get_extra_capabilities_with_values(self):
|
||||
ComputeHostExtraCapability = collections.namedtuple(
|
||||
'ComputeHostExtraCapability',
|
||||
['id', 'property_id', 'capability_value', 'computehost_id'])
|
||||
self.db_host_extra_capability_get_all_per_host.return_value = [
|
||||
{'id': 1,
|
||||
'capability_name': 'foo',
|
||||
'capability_value': 'bar',
|
||||
'other': 'value',
|
||||
'computehost_id': 1
|
||||
},
|
||||
{'id': 2,
|
||||
'capability_name': 'buzz',
|
||||
'capability_value': 'word',
|
||||
'computehost_id': 1
|
||||
}]
|
||||
(ComputeHostExtraCapability(1, 'foo', 'bar', 1), 'foo'),
|
||||
(ComputeHostExtraCapability(2, 'buzz', 'word', 1), 'buzz')]
|
||||
|
||||
res = self.fake_phys_plugin._get_extra_capabilities(1)
|
||||
self.assertEqual({'foo': 'bar', 'buzz': 'word'}, res)
|
||||
|
||||
|
@ -229,7 +225,7 @@ class PhysicalHostPluginTestCase(tests.TestCase):
|
|||
# NOTE(sbauza): 'id' will be pop'd, we need to keep track of it
|
||||
fake_request = fake_host.copy()
|
||||
fake_capa = {'computehost_id': '1',
|
||||
'capability_name': 'foo',
|
||||
'property_name': 'foo',
|
||||
'capability_value': 'bar',
|
||||
}
|
||||
self.get_extra_capabilities.return_value = {'foo': 'bar'}
|
||||
|
@ -296,11 +292,10 @@ class PhysicalHostPluginTestCase(tests.TestCase):
|
|||
host_values = {'foo': 'baz'}
|
||||
|
||||
self.db_host_extra_capability_get_all_per_name.return_value = [
|
||||
{'id': 'extra_id1',
|
||||
'computehost_id': self.fake_host_id,
|
||||
'capability_name': 'foo',
|
||||
'capability_value': 'bar'
|
||||
},
|
||||
({'id': 'extra_id1',
|
||||
'computehost_id': self.fake_host_id,
|
||||
'capability_value': 'bar'},
|
||||
'foo'),
|
||||
]
|
||||
|
||||
self.get_reservations_by_host = self.patch(
|
||||
|
@ -310,7 +305,7 @@ class PhysicalHostPluginTestCase(tests.TestCase):
|
|||
self.fake_phys_plugin.update_computehost(self.fake_host_id,
|
||||
host_values)
|
||||
self.db_host_extra_capability_update.assert_called_once_with(
|
||||
'extra_id1', {'capability_name': 'foo', 'capability_value': 'baz'})
|
||||
'extra_id1', {'capability_value': 'baz'})
|
||||
|
||||
def test_update_host_having_issue_when_storing_extra_capability(self):
|
||||
def fake_db_host_extra_capability_update(*args, **kwargs):
|
||||
|
@ -320,11 +315,10 @@ class PhysicalHostPluginTestCase(tests.TestCase):
|
|||
self.db_utils, 'get_reservations_by_host_id')
|
||||
self.get_reservations_by_host.return_value = []
|
||||
self.db_host_extra_capability_get_all_per_name.return_value = [
|
||||
{'id': 'extra_id1',
|
||||
'computehost_id': self.fake_host_id,
|
||||
'capability_name': 'foo',
|
||||
'capability_value': 'bar'
|
||||
},
|
||||
({'id': 'extra_id1',
|
||||
'computehost_id': self.fake_host_id,
|
||||
'capability_value': 'bar'},
|
||||
'foo'),
|
||||
]
|
||||
fake = self.db_host_extra_capability_update
|
||||
fake.side_effect = fake_db_host_extra_capability_update
|
||||
|
@ -340,7 +334,7 @@ class PhysicalHostPluginTestCase(tests.TestCase):
|
|||
host_values)
|
||||
self.db_host_extra_capability_create.assert_called_once_with({
|
||||
'computehost_id': '1',
|
||||
'capability_name': 'qux',
|
||||
'property_name': 'qux',
|
||||
'capability_value': 'word'
|
||||
})
|
||||
|
||||
|
@ -348,11 +342,10 @@ class PhysicalHostPluginTestCase(tests.TestCase):
|
|||
host_values = {'foo': 'buzz'}
|
||||
|
||||
self.db_host_extra_capability_get_all_per_name.return_value = [
|
||||
{'id': 'extra_id1',
|
||||
'computehost_id': self.fake_host_id,
|
||||
'capability_name': 'foo',
|
||||
'capability_value': 'bar'
|
||||
},
|
||||
({'id': 'extra_id1',
|
||||
'computehost_id': self.fake_host_id,
|
||||
'capability_value': 'bar'},
|
||||
'foo'),
|
||||
]
|
||||
fake_phys_reservation = {
|
||||
'resource_type': plugin.RESOURCE_TYPE,
|
||||
|
@ -2388,6 +2381,74 @@ class PhysicalHostPluginTestCase(tests.TestCase):
|
|||
self.fake_phys_plugin._check_params(values)
|
||||
self.assertEqual(values['before_end'], 'default')
|
||||
|
||||
def test_list_resource_properties(self):
|
||||
self.db_list_resource_properties = self.patch(
|
||||
self.db_api, 'resource_properties_list')
|
||||
|
||||
# Expecting a list of (Reservation, Allocation)
|
||||
self.db_list_resource_properties.return_value = [
|
||||
('prop1', False, 'aaa'),
|
||||
('prop1', False, 'bbb'),
|
||||
('prop2', False, 'aaa'),
|
||||
('prop2', False, 'aaa'),
|
||||
('prop3', True, 'aaa')
|
||||
]
|
||||
|
||||
expected = [
|
||||
{'property': 'prop1'},
|
||||
{'property': 'prop2'}
|
||||
]
|
||||
|
||||
ret = self.fake_phys_plugin.list_resource_properties(
|
||||
query={'detail': False})
|
||||
|
||||
# Sort returned value to use assertListEqual
|
||||
ret.sort(key=lambda x: x['property'])
|
||||
|
||||
self.assertListEqual(expected, ret)
|
||||
self.db_list_resource_properties.assert_called_once_with(
|
||||
'physical:host')
|
||||
|
||||
def test_list_resource_properties_with_detail(self):
|
||||
self.db_list_resource_properties = self.patch(
|
||||
self.db_api, 'resource_properties_list')
|
||||
|
||||
# Expecting a list of (Reservation, Allocation)
|
||||
self.db_list_resource_properties.return_value = [
|
||||
('prop1', False, 'aaa'),
|
||||
('prop1', False, 'bbb'),
|
||||
('prop2', False, 'ccc'),
|
||||
('prop3', True, 'aaa')
|
||||
]
|
||||
|
||||
expected = [
|
||||
{'property': 'prop1', 'private': False, 'values': ['aaa', 'bbb']},
|
||||
{'property': 'prop2', 'private': False, 'values': ['ccc']}
|
||||
]
|
||||
|
||||
ret = self.fake_phys_plugin.list_resource_properties(
|
||||
query={'detail': True})
|
||||
|
||||
# Sort returned value to use assertListEqual
|
||||
ret.sort(key=lambda x: x['property'])
|
||||
|
||||
self.assertListEqual(expected, ret)
|
||||
self.db_list_resource_properties.assert_called_once_with(
|
||||
'physical:host')
|
||||
|
||||
def test_update_resource_property(self):
|
||||
resource_property_values = {
|
||||
'resource_type': 'physical:host',
|
||||
'private': False}
|
||||
|
||||
db_resource_property_update = self.patch(
|
||||
self.db_api, 'resource_property_update')
|
||||
|
||||
self.fake_phys_plugin.update_resource_property(
|
||||
'foo', resource_property_values)
|
||||
db_resource_property_update.assert_called_once_with(
|
||||
'physical:host', 'foo', resource_property_values)
|
||||
|
||||
|
||||
class PhysicalHostMonitorPluginTestCase(tests.TestCase):
|
||||
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"resource_properties": [
|
||||
{
|
||||
"property": "gpu",
|
||||
"private": false,
|
||||
"values": [
|
||||
"True",
|
||||
"False"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"resource_properties": [
|
||||
{
|
||||
"property": "gpu"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"resource_property": {
|
||||
"created_at": "2021-12-15T19:38:16.000000",
|
||||
"updated_at": "2021-12-21T21:37:19.000000",
|
||||
"id": "19e48cd0-042d-4044-a69a-d44d672849b5",
|
||||
"resource_type": "physical:host",
|
||||
"property_name": "gpu",
|
||||
"private": true
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"private": true
|
||||
}
|
|
@ -51,6 +51,49 @@ Result:
|
|||
|
||||
..
|
||||
|
||||
3. (Optional) Add extra capabilities to host to add other properties. These can
|
||||
be used to filter hosts when creating a reservation.
|
||||
|
||||
.. sourcecode:: console
|
||||
|
||||
# Using the blazar CLI
|
||||
blazar host-update --extra gpu=True compute-1
|
||||
|
||||
# Using the openstack CLI
|
||||
openstack reservation host set --extra gpu=True compute-1
|
||||
|
||||
..
|
||||
|
||||
Result:
|
||||
|
||||
.. sourcecode:: console
|
||||
|
||||
Updated host: compute-1
|
||||
|
||||
..
|
||||
|
||||
Multiple ``--extra`` parameters can be included. They can also be specified in
|
||||
``host-create``. Properties can be made private or public. By default, they
|
||||
are public.
|
||||
|
||||
.. sourcecode:: console
|
||||
|
||||
# Using the blazar CLI
|
||||
blazar host-capability-update gpu --private
|
||||
|
||||
# Using the openstack CLI
|
||||
openstack reservation host capability update gpu --private
|
||||
|
||||
..
|
||||
|
||||
Result:
|
||||
|
||||
.. sourcecode:: console
|
||||
|
||||
Updated host extra capability: gpu
|
||||
|
||||
..
|
||||
|
||||
2. Create a lease
|
||||
-----------------
|
||||
|
||||
|
@ -128,6 +171,103 @@ Result:
|
|||
|
||||
..
|
||||
|
||||
3. Alternatively, create leases with resource properties.
|
||||
First list properties.
|
||||
|
||||
.. sourcecode:: console
|
||||
|
||||
# Using the blazar CLI
|
||||
blazar host-capability-list
|
||||
|
||||
# Using the openstack CLI
|
||||
openstack reservation host capability list
|
||||
|
||||
..
|
||||
|
||||
Result:
|
||||
|
||||
.. sourcecode:: console
|
||||
|
||||
+----------+
|
||||
| property |
|
||||
+----------+
|
||||
| gpu |
|
||||
+----------+
|
||||
|
||||
..
|
||||
|
||||
List possible values for a property
|
||||
|
||||
.. sourcecode:: console
|
||||
|
||||
# Using the blazar CLI
|
||||
blazar host-capability-show gpu
|
||||
|
||||
# Using the openstack CLI
|
||||
openstack reservation host capability show gpu
|
||||
|
||||
..
|
||||
|
||||
Result:
|
||||
|
||||
.. sourcecode:: console
|
||||
|
||||
+-------------------+-------+
|
||||
| Field | Value |
|
||||
+-------------------+-------+
|
||||
| capability_values | True |
|
||||
| | False |
|
||||
| private | False |
|
||||
| property | gpu |
|
||||
+-------------------+-------+
|
||||
|
||||
..
|
||||
|
||||
Create a lease.
|
||||
|
||||
.. sourcecode:: console
|
||||
|
||||
# Using the blazar CLI
|
||||
blazar lease-create --physical-reservation min=1,max=1,resource_properties='["=", "$gpu", "True"]' --start-date "2020-06-08 12:00" --end-date "2020-06-09 12:00" lease-1
|
||||
|
||||
# Using the openstack CLI
|
||||
openstack reservation lease create --reservation resource_type=physical:host,min=1,max=1,resource_properties='[">=", "$vcpus", "2"]' --start-date "2020-06-08 12:00" --end-date "2020-06-09 12:00" lease-1
|
||||
|
||||
..
|
||||
|
||||
Result:
|
||||
|
||||
.. sourcecode:: console
|
||||
|
||||
+---------------+---------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
| Field | Value |
|
||||
+---------------+---------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
| action | |
|
||||
| created_at | 2020-06-08 02:43:40 |
|
||||
| end_date | 2020-06-09T12:00:00.000000 |
|
||||
| events | {"status": "UNDONE", "lease_id": "6638c31e-f6c8-4982-9b98-d2ca0a8cb646", "event_type": "before_end_lease", "created_at": "2020-06-08 |
|
||||
| | 02:43:40", "updated_at": null, "time": "2020-06-08T12:00:00.000000", "id": "420caf25-dba5-4ac3-b377-50503ea5c886"} |
|
||||
| | {"status": "UNDONE", "lease_id": "6638c31e-f6c8-4982-9b98-d2ca0a8cb646", "event_type": "start_lease", "created_at": "2020-06-08 02:43:40", |
|
||||
| | "updated_at": null, "time": "2020-06-08T12:00:00.000000", "id": "b9696139-55a1-472d-baff-5fade2c15243"} |
|
||||
| | {"status": "UNDONE", "lease_id": "6638c31e-f6c8-4982-9b98-d2ca0a8cb646", "event_type": "end_lease", "created_at": "2020-06-08 02:43:40", |
|
||||
| | "updated_at": null, "time": "2020-06-09T12:00:00.000000", "id": "ff9e6f52-db50-475a-81f1-e6897fdc769d"} |
|
||||
| id | 6638c31e-f6c8-4982-9b98-d2ca0a8cb646 |
|
||||
| name | lease-1 |
|
||||
| project_id | 4527fa2138564bd4933887526d01bc95 |
|
||||
| reservations | {"status": "pending", "lease_id": "6638c31e-f6c8-4982-9b98-d2ca0a8cb646", "resource_id": "8", "max": 1, "created_at": "2020-06-08 |
|
||||
| | 02:43:40", "min": 1, "updated_at": null, "hypervisor_properties": "", "resource_properties": "[\"=\", \"$gpu\", \"True\"]", "id": |
|
||||
| | "4d3dd68f-0e3f-4f6b-bef7-617525c74ccb", "resource_type": "physical:host"} |
|
||||
| start_date | 2020-06-08T12:00:00.000000 |
|
||||
| status | |
|
||||
| status_reason | |
|
||||
| trust_id | ba4c321878d84d839488216de0a9e945 |
|
||||
| updated_at | |
|
||||
| user_id | |
|
||||
+---------------+---------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
|
||||
..
|
||||
|
||||
|
||||
3. Use the leased resources
|
||||
---------------------------
|
||||
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
---
|
||||
features:
|
||||
- |
|
||||
Adds a host resource property discovery API, which allows users to
|
||||
enumerate what properties are available, and current property values.
|
||||
Properties can be made private by operators, which filters them from the
|
||||
public list.
|
Loading…
Reference in New Issue