add patch upgrade history

Change-Id: I542786f31c9118c802def968381ac43850d10e52
This commit is contained in:
meizhifang 2017-02-28 16:10:02 +08:00
parent 1016c7da42
commit 0ec893e8cb
16 changed files with 903 additions and 11 deletions

View File

@ -1282,3 +1282,10 @@ def upgrade_os(req, version_id, version_patch_id, update_file,
host_meta['version_patch_id'] = version_patch_id
daisy_cmn.update_db_host_status(req, host['id'], host_meta,
version_id)
if version_patch_id:
patch_meta = {'host_id': host['id'],
'patch_name': update_file,
'version_id': version_id,
'type': update_object}
registry.add_host_patch_history_metadata(
req.context, patch_meta)

View File

@ -596,6 +596,14 @@ class API(wsgi.Router):
controller=version_patchs_resource,
action='update_version_patch',
conditions={'method': ['PUT']})
mapper.connect("/patch_history",
controller=version_patchs_resource,
action='add_host_patch_history',
conditions={'method': ['POST']})
mapper.connect("/patch_history/list",
controller=version_patchs_resource,
action='list_host_patch_history',
conditions={'method': ['GET']})
template_configs_resource = template_configs.create_resource()
mapper.connect("/template_configs/import_template_config",

55
code/daisy/daisy/api/v1/version_patchs.py Normal file → Executable file
View File

@ -190,6 +190,44 @@ class Controller(controller.BaseController):
version_patch_meta = self.get_version_patch_meta_or_404(req, id)
return {'version_patch_meta': version_patch_meta}
@utils.mutating
def add_host_patch_history(self, req, version_patch_meta):
"""
Returns metadata about an patch history in the HTTP headers of the
response object
:param req: The WSGI/Webob Request object
:param id: The opaque version patch identifier
"""
self._enforce(req, 'add_host_patch_history')
patch_history_meta = \
registry.add_host_patch_history_metadata(req.context,
version_patch_meta)
return {'patch_history_meta': patch_history_meta}
@utils.mutating
def list_host_patch_history(self, req):
"""
Returns metadata about an patch history in the HTTP headers of the
response object
:param req: The WSGI/Webob Request object
:param id: The opaque version patch identifier
:raises HTTPNotFound if version patch metadata is not available to user
"""
self._enforce(req, 'get_host_patch_history')
params = self._get_query_params(req)
try:
patch_history_meta = registry.list_host_patch_history_metadata(
req.context, **params)
except Exception:
msg = "host patch history list error"
LOG.error(msg)
raise HTTPBadRequest(explanation=msg, request=req)
return dict(patch_history_meta=patch_history_meta)
@utils.mutating
def update_version_patch(self, req, id, version_patch_meta):
"""
@ -257,6 +295,9 @@ class VersionPatchDeserializer(wsgi.JSONRequestDeserializer):
def add_version_patch(self, request):
return self._deserialize(request)
def add_host_patch_history(self, request):
return self._deserialize(request)
def update_version_patch(self, request):
return self._deserialize(request)
@ -274,6 +315,13 @@ class VersionPatchserializer(wsgi.JSONResponseSerializer):
response.body = self.to_json(dict(version_patch=version_patch_meta))
return response
def add_host_patch_history(self, response, result):
version_patch_meta = result['patch_history_meta']
response.status = 201
response.headers['Content-Type'] = 'application/json'
response.body = self.to_json(dict(patch_history=version_patch_meta))
return response
def delete_version_patch(self, response, result):
version_patch_meta = result['version_patch_meta']
response.status = 201
@ -288,6 +336,13 @@ class VersionPatchserializer(wsgi.JSONResponseSerializer):
response.body = self.to_json(dict(version_patch=version_patch_meta))
return response
def list_host_patch_history(self, response, result):
history_meta = result['patch_history_meta']
response.status = 201
response.headers['Content-Type'] = 'application/json'
response.body = self.to_json(dict(patch_history=history_meta))
return response
def create_resource():
"""versions resource factory method"""

124
code/daisy/daisy/db/sqlalchemy/api.py Normal file → Executable file
View File

@ -5898,6 +5898,121 @@ def _version_patch_update(context, values, version_patch_id):
return version_patch_get(context, version_patch_ref.id)
@retry(retry_on_exception=_retry_on_deadlock, wait_fixed=500,
stop_max_attempt_number=50)
def add_host_patch_history(context, values):
"""add version to daisy."""
return _host_patch_history_update(context, values, None)
def _host_patch_history_update(context, values, patch_history_id):
"""update or add patch history to daisy."""
values = values.copy()
session = get_session()
with session.begin():
if patch_history_id:
patch_history_ref = _patch_history_get(context, patch_history_id,
session=session)
else:
patch_history_ref = models.HostPatchHistory()
if patch_history_id:
# Don't drop created_at if we're passing it in...
_drop_protected_attrs(models.HostPatchHistory, values)
# NOTE(iccha-sethi): updated_at must be explicitly set in case
# only ImageProperty table was modifited
values['updated_at'] = timeutils.utcnow()
if patch_history_id:
if values.get('id', None): del values['id']
patch_history_ref.update(values)
_update_values(patch_history_ref, values)
try:
patch_history_ref.save(session=session)
except db_exception.DBDuplicateEntry:
raise exception.Duplicate("version patch ID %s already exists!"
% values['id'])
else:
patch_history_ref.update(values)
_update_values(patch_history_ref, values)
try:
patch_history_ref.save(session=session)
except db_exception.DBDuplicateEntry:
raise exception.Duplicate("version patch ID %s already exists!"
% values['id'])
return patch_history_get(context, patch_history_ref.id)
def _patch_history_get(context, id, session=None,
force_show_deleted=False):
"""Get an patch history or raise if it does not exist."""
session = session or get_session()
try:
query = session.query(models.HostPatchHistory).filter_by(
id=id)
# filter out deleted images if context disallows it
if not force_show_deleted and not context.can_see_deleted:
query = query.filter_by(deleted=False)
patch_history = query.one()
return patch_history
except sa_orm.exc.NoResultFound:
msg = "No patch history patch found with ID %s" % id
LOG.debug(msg)
raise exception.NotFound(msg)
def patch_history_get(context, id, session=None,
force_show_deleted=False):
patch_history = _patch_history_get(context, id,
session=session,
force_show_deleted=force_show_deleted)
return patch_history
def list_host_patch_history(context, filters=None, marker=None, limit=None,
sort_key=None, sort_dir=None):
sort_key = ['created_at'] if not sort_key else sort_key
default_sort_dir = 'desc'
if not sort_dir:
sort_dir = [default_sort_dir] * len(sort_key)
elif len(sort_dir) == 1:
default_sort_dir = sort_dir[0]
sort_dir *= len(sort_key)
filters = filters or {}
showing_deleted = False
for key in ['created_at', 'id']:
if key not in sort_key:
sort_key.append(key)
sort_dir.append(default_sort_dir)
session = get_session()
query = session.query(models.HostPatchHistory).filter_by(deleted=showing_deleted)
if 'host_id' in filters and 'version_id' in filters:
host_id = filters.pop('host_id')
version_id = filters.pop('version_id')
query = session.query(models.HostPatchHistory).\
filter_by(deleted=False).filter_by(host_id=host_id).\
filter_by(version_id=version_id)
if 'host_id' in filters:
host_id = filters.pop('host_id')
query = session.query(models.HostPatchHistory).\
filter_by(deleted=False).filter_by(host_id=host_id)
if 'type' in filters:
type = filters.pop('type')
query = session.query(models.HostPatchHistory).\
filter_by(deleted=False).filter_by(type=type)
patchs = []
for patch_history in query.all():
patch = patch_history.to_dict()
version_ref = _version_get(context, patch['version_id'])
if version_ref:
patch['version_name'] = version_ref.name
patchs.append(patch)
return patchs
def _version_patch_get(context, version_patch_id, session=None,
force_show_deleted=False):
"""Get an version patch or raise if it does not exist."""
@ -5990,8 +6105,13 @@ def version_patch_get_all(context, filters=None, marker=None, limit=None,
version_patchs = []
for version_patch in query.all():
version_dict = version_patch.to_dict()
version_sql = "select * from hosts where (hosts.version_patch_id ='"\
+ version_dict['id'] + "' and hosts.deleted=0)"
version_sql = "select * from hosts, host_patch_history where" \
" (hosts.version_patch_id ='" + version_dict['id'] +\
"' and hosts.deleted=0) or (hosts.id =" \
"host_patch_history.host_id and " \
"host_patch_history.patch_name='" + \
version_dict['name'] +"' and hosts.deleted=0 and " \
"host_patch_history.deleted=0)"
hosts_number = session.execute(version_sql).fetchone()
if hosts_number:
version_dict['status'] = "used"

View File

@ -0,0 +1,58 @@
# Copyright 2013 OpenStack Foundation
# All Rights Reserved.
#
# 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 sqlalchemy.schema import (Column, MetaData, Table)
from daisy.db.sqlalchemy.migrate_repo.schema import (Boolean, DateTime,
String, create_tables,
drop_tables)
def define_host_patch_history_table(meta):
host_patch_history = Table('host_patch_history',
meta,
Column('id', String(36),
primary_key=True,
nullable=False),
Column('host_id', String(36), nullable=False),
Column('type', String(30), nullable=False),
Column('version_id', String(36)),
Column('patch_name', String(255)),
Column('created_at', DateTime(),
nullable=False),
Column('updated_at', DateTime(),
nullable=False),
Column('deleted_at', DateTime()),
Column('deleted',
Boolean(),
nullable=False,
default=False,
index=True),
mysql_engine='InnoDB',
extend_existing=True)
return host_patch_history
def upgrade(migrate_engine):
meta = MetaData()
meta.bind = migrate_engine
tables = [define_host_patch_history_table(meta)]
create_tables(tables)
def downgrade(migrate_engine):
meta = MetaData()
meta.bind = migrate_engine
tables = [define_host_patch_history_table(meta)]
drop_tables(tables)

View File

@ -764,6 +764,18 @@ class ConfigService(BASE, DaisyBase):
nullable=False)
class HostPatchHistory(BASE, DaisyBase):
"""Represents an component config in the datastore."""
__tablename__ = 'host_patch_history'
__table_args__ = (Index('ix_host_patch_history_deleted', 'deleted'),)
host_id = Column(String(36), nullable=False)
type = Column(String(30), nullable=False)
version_id = Column(String(36), nullable=False)
patch_name = Column(String(255), nullable=False)
def register_models(engine):
"""Create database tables for all models with the given engine."""
models = (Hwm, Host, DiscoverHost, Cluster, ClusterHost, Template,
@ -774,7 +786,8 @@ def register_models(engine):
Subnet, FloatIpRange, DnsNameservers, Router, ServiceDisk,
CinderVolume, OpticalSwitch, Version, VersionPatch,
TemplateConfig, TemplateFunc, TemplateFuncConfigs,
TemplateService, ConfigService, NeutronBackend)
TemplateService, ConfigService, NeutronBackend,
HostPatchHistory)
for model in models:
model.metadata.create_all(engine)
@ -789,6 +802,7 @@ def unregister_models(engine):
Subnet, FloatIpRange, DnsNameservers, Router, ServiceDisk,
CinderVolume, OpticalSwitch, Version, VersionPatch,
TemplateConfig, TemplateFunc, TemplateFuncConfigs,
TemplateService, ConfigService, NeutronBackend)
TemplateService, ConfigService, NeutronBackend,
HostPatchHistory)
for model in models:
model.metadata.drop_all(engine)

View File

@ -547,6 +547,14 @@ def init(mapper):
controller=version_patch_resource,
action='get_version_patch',
conditions={'method': ['GET']})
mapper.connect("/patch_history",
controller=version_patch_resource,
action='add_host_patch_history',
conditions={'method': ['POST']})
mapper.connect("/patch_history/list",
controller=version_patch_resource,
action='list_patch_history',
conditions={'method': ['GET']})
template_configs_resource = template_configs.create_resource()
mapper.connect("/import_template_configs",

63
code/daisy/daisy/registry/api/v1/version_patchs.py Normal file → Executable file
View File

@ -42,7 +42,8 @@ DISPLAY_FIELDS_IN_INDEX = ['id', 'name', 'size',
'version_id', 'container_format',
'checksum']
SUPPORTED_FILTERS = ['name', 'status', 'version_id',
SUPPORTED_FILTERS = ['name', 'status', 'version_id', 'host_id',
'patch_name', 'version_name', 'version_id',
'changes-since', 'protected', 'type']
SUPPORTED_SORT_KEYS = ('name', 'status', 'version_id', 'disk_format',
@ -179,6 +180,26 @@ class Controller(object):
return None
return strutils.bool_from_string(deleted)
def _list_patch_history(self, context, filters, **params):
"""Get patch history, wrapping in exception if necessary."""
try:
return self.db_api.list_host_patch_history(context,
filters=filters,
**params)
except exception.NotFound:
LOG.warn(_LW("Invalid marker patch history %(id)s could not be "
"found.") % {'id': params.get('marker')})
msg = _("Invalid marker. Host could not be found.")
raise exc.HTTPBadRequest(explanation=msg)
except exception.Forbidden:
LOG.warn(_LW("Access denied to patch history %(id)s but returning "
"'not found'") % {'id': params.get('marker')})
msg = _("Invalid marker. Host could not be found.")
raise exc.HTTPBadRequest(explanation=msg)
except Exception:
LOG.exception(_LE("Unable to get patch history"))
raise
@utils.mutating
def add_version_patch(self, req, body):
"""Registers a new version with the registry.
@ -337,6 +358,46 @@ class Controller(object):
% version_patch_id)
raise
@utils.mutating
def list_patch_history(self, req):
"""Return data about the given patch_history."""
params = self._get_query_params(req)
patch_history = self._list_patch_history(req.context, **params)
return dict(patch_history=patch_history)
@utils.mutating
def add_host_patch_history(self, req, body):
"""Registers a new version with the registry.
:param req: wsgi Request object
:param body: Dictionary of information about the version
:retval Returns the newly-created version information as a mapping,
which will include the newly-created version's internal id
in the 'id' field
"""
patch_history_meta = body["patch_history"]
try:
patch_history = self.db_api.add_host_patch_history(
req.context, patch_history_meta)
version_data = dict(patch_history=patch_history)
return version_data
except exception.Duplicate:
msg = (_("patch history with identifier %s already exists!") %
patch_history_meta['patch_name'])
LOG.warn(msg)
return exc.HTTPConflict(msg)
except exception.Invalid as e:
msg = (_("Failed to add patch history metadata. "
"Got error: %s") % utils.exception_to_str(e))
LOG.error(msg)
return exc.HTTPBadRequest(msg)
except Exception:
LOG.exception(_LE("Unable to create patch history %s"),
patch_history_meta['patch_name'])
raise
def _limit_locations(image):
locations = image.pop('locations', [])

View File

@ -719,6 +719,16 @@ def add_version_patch_metadata(context, version_patch_meta):
return c.add_version_patch(version_patch_meta)
def add_host_patch_history_metadata(context, patch_history_meta):
c = get_registry_client(context)
return c.add_host_patch_history(patch_history_meta)
def list_host_patch_history_metadata(context, **kwargs):
c = get_registry_client(context)
return c.list_host_patch_history(**kwargs)
def delete_version_patch_metadata(context, version_patch_id):
LOG.debug("Deleting version metadata %s...", version_patch_id)
c = get_registry_client(context)

View File

@ -32,6 +32,7 @@ from daisy.registry.api.v1 import networks
from daisy.registry.api.v1 import template
from daisy.registry.api.v1 import hwms
from daisy.registry.api.v1 import versions
from daisy.registry.api.v1 import version_patchs
from daisy.registry.api.v1 import disk_array
from daisy.registry.api.v1 import template_configs
from daisy.registry.api.v1 import template_funcs
@ -1428,6 +1429,31 @@ class RegistryClient(BaseClient):
data = jsonutils.loads(res.read())['version_patch']
return data
def add_host_patch_history(self, patch_history_metadata):
headers = {
'Content-Type': 'application/json',
}
if 'patch_history' not in patch_history_metadata:
patch_history_metadata = dict(patch_history=patch_history_metadata)
body = jsonutils.dumps(patch_history_metadata)
res = self.do_request(
"POST",
"/patch_history",
body=body,
headers=headers)
data = jsonutils.loads(res.read())
return data['patch_history']
def list_host_patch_history(self, **kwargs):
"""Return a list of version patch associations from Registry."""
params = self._extract_params(kwargs, version_patchs.SUPPORTED_PARAMS)
res = self.do_request("GET", "/patch_history/list", params=params)
version_list = jsonutils.loads(res.read())['patch_history']
return version_list
def get_template_config(self, template_config_id):
"""Return a list of template config associations from Registry."""
res = self.do_request("GET", "/template_configs/%s" %

View File

@ -409,6 +409,31 @@ class TestOsInstall(test.TestCase):
install.upgrade_os(req, version_id, version_patch_id, update_file,
hosts_list, update_object)
@mock.patch('daisy.api.backends.common.update_db_host_status')
@mock.patch("daisy.api.backends.common.get_host_detail")
@mock.patch('daisy.api.backends.osinstall.pxe.install.'
'os_thread_bin')
@mock.patch('daisy.registry.client.v1.api.add_host_patch_history_metadata')
def test_upgrade_os_patch_active(self, mock_patch_history,
mock_os_thread_in,
mock_get_host_detail,
mock_do_update_db_status):
req = webob.Request.blank('/')
req.context = RequestContext(is_admin=True, user='fake user',
tenant='fake tenant')
version_id = "1"
version_patch_id = "123"
update_file = "test"
hosts_list = [{'10.43.177.1': {'id': "1"}}]
update_object = "vplat"
mock_do_update_db_status.return_value = {}
mock_patch_history.return_value = {}
mock_get_host_detail.return_value = {'os_status': 'active'}
mock_os_thread_in.return_value = {}
install.upgrade_os(req, version_id, version_patch_id, update_file,
hosts_list, update_object)
self.assertEqual(1, mock_patch_history.call_count)
@mock.patch('subprocess.check_output')
def test_exec_upgrade_with_vplat(self, mock_check_output):
host_ip = '127.0.0.1'

View File

@ -0,0 +1,209 @@
import mock
import webob
import json as jsonutils
from daisy.api.v1 import version_patchs
from daisy.context import RequestContext
from daisy import test
def set_version_meta():
version_patch_meta = {}
version_patch_meta["name"] = "test_version"
version_patch_meta["description"] = "111"
version_patch_meta["status"] = "used"
return version_patch_meta
class TestVersionsPatchApiConfig(test.TestCase):
def setUp(self):
super(TestVersionsPatchApiConfig, self).setUp()
self.controller = version_patchs.Controller()
@mock.patch('daisy.registry.client.v1.client.RegistryClient.'
'do_request')
def test_add_version_patch(self, mock_do_request):
version_patch_meta = set_version_meta()
req = webob.Request.blank('/')
req.context = RequestContext(is_admin=True,
user='fake user',
tenant='fake tenamet')
def fake_do_request(method, path, **params):
res = mock.Mock()
if method == 'POST':
post_result = {
u'version_patch': {u'status': u'used',
u'name': u'name3',
u'deleted': False, u'checksum': None,
u'created_at': u'2016-07-12',
u'description': None,
u'status': u'used',
u'deleted_at': None,
u'size': None}}
res.read.return_value = jsonutils.dumps(post_result)
return res
mock_do_request.side_effect = fake_do_request
add_version = self.controller.add_version_patch(req,
version_patch_meta)
self.assertEqual("name3",
add_version['version_patch_meta']['name'])
def test_add_version_patch_with_no_name(self):
req = webob.Request.blank('/')
req.context = RequestContext(is_admin=True,
user='fake user',
tenant='fake tenamet')
version_meta = {}
version_meta["description"] = "111"
self.assertRaises(ValueError, self.controller.add_version_patch, req,
version_meta)
@mock.patch('daisy.registry.client.v1.client.RegistryClient.'
'do_request')
def test_update_version_patch(self, mock_do_request):
req = webob.Request.blank('/')
req.context = RequestContext(is_admin=True,
user='fake user',
tenant='fake tenamet')
def fake_do_request(method, path, **params):
res = mock.Mock()
if method == "GET":
get_result = {
"version_patch": {"status": "unused",
"name": "ssh2.exe",
"checksum": "60489112c272fbf",
"size": 1089536,
"id": "1",
"description": "1",
"deleted": 0}}
res.read.return_value = jsonutils.dumps(get_result)
return res
elif method == 'PUT':
post_result = {
u'version_patch': {u'status': u'unused',
u'name': u'test',
u'deleted': 0,
u'checksum': None,
u'description': None,
u'deleted_at': None,
u'size': None}}
res.read.return_value = jsonutils.dumps(post_result)
return res
mock_do_request.side_effect = fake_do_request
version_patch = {}
version_patch["name"] = "test"
version_patch["description"] = "111"
version_patch_id = "1"
add_version = self.controller.update_version_patch(req,
version_patch_id,
version_patch)
self.assertEqual("test",
add_version['version_patch_meta']['name'])
@mock.patch('daisy.registry.client.v1.client.RegistryClient.'
'do_request')
def test_get_version_patch(self, mock_do_request):
version_patch_id = "34811a0e66792a979e99"
req = webob.Request.blank('/')
req.context = RequestContext(is_admin=True,
user='fake user',
tenant='fake tenamet')
def fake_do_request(method, path, **params):
res = mock.Mock()
if method == "GET":
get_result = {
"version_patch": {"status": "unused",
"name": "ssh2.exe",
"checksum": "60489112c2c0862fbf",
"size": 1089536,
"id": "34811a0e66792a979e99",
"description": "azsdadsad"}}
res.read.return_value = jsonutils.dumps(get_result)
return res
mock_do_request.side_effect = fake_do_request
version = self.controller.get_version_patch(req, version_patch_id)
self.assertEqual(version_patch_id,
version['version_patch_meta']['id'])
@mock.patch('daisy.registry.client.v1.client.RegistryClient.'
'do_request')
def test_delete_version_patch(self, mock_do_request):
version_patch_id = "34811a0e-a69f-4dd3-bbfb-66792a979e99"
req = webob.Request.blank('/')
req.context = RequestContext(is_admin=True,
user='fake user',
tenant='fake tenamet')
def fake_do_request(method, path, **params):
res = mock.Mock()
if method == "GET":
get_result = {
"version_patch": {"status": "unused",
"name": "ssh2.exe",
"checksum": "60489112c277862fbf",
"size": 1089536,
"id": "34811a0e-a69f-2a979e99",
"description": "azsad"}}
res.read.return_value = jsonutils.dumps(get_result)
return res
elif method == "DELETE":
result = {
"version_patch": {"status": "unused",
"name": "ssh2.exe",
"checksum": "60489112c277a18147b",
"created_at": "2016-07-12",
"size": 1089536,
"updated_at": "2016-07-12",
"id": "34811a0e-a69f-4dd3",
"description": "azsdadsad"}}
res.read.return_value = jsonutils.dumps(result)
return res
mock_do_request.side_effect = fake_do_request
version = self.controller.delete_version_patch(req, version_patch_id)
self.assertEqual(200, version.status_code)
@mock.patch('daisy.registry.client.v1.api.'
'add_host_patch_history_metadata')
def test_add_host_patch_history(self, mock_add_history):
history_meta = {'patch_name': 'test'}
req = webob.Request.blank('/')
req.context = RequestContext(is_admin=True,
user='fake user',
tenant='fake tenamet')
mock_add_history.return_value = history_meta
add_history = self.controller.add_host_patch_history(req, history_meta)
self.assertEqual({'patch_history_meta': history_meta}, add_history)
@mock.patch('daisy.registry.client.v1.api.'
'list_host_patch_history_metadata')
def test_list_host_patch_history(self, mock_list_history):
history_meta = {'patch_name': 'test'}
req = webob.Request.blank('/')
req.context = RequestContext(is_admin=True,
user='fake user',
tenant='fake tenamet')
mock_list_history.return_value = history_meta
historys = self.controller.list_host_patch_history(req)
self.assertEqual({'patch_history_meta': history_meta}, historys)
@mock.patch('daisy.registry.client.v1.api.'
'list_host_patch_history_metadata')
def test_list_host_patch_history_with_except(self, mock_list_history):
def mock_history(*args, **kwargs):
raise webob.exc.HTTPBadRequest
req = webob.Request.blank('/')
req.context = RequestContext(is_admin=True,
user='fake user',
tenant='fake tenamet')
mock_list_history.side_effect = mock_history
self.assertRaises(webob.exc.HTTPBadRequest,
self.controller.list_host_patch_history, req)

View File

@ -3,6 +3,7 @@ from daisy.context import RequestContext
from daisy.db.sqlalchemy import api
from daisy import test
from daisy.tests import test_utils
from daisy.db.sqlalchemy import models
import mock
from oslo_db.sqlalchemy import session
from oslo_db.sqlalchemy.session import Query
@ -759,7 +760,6 @@ class TestSqlalchemyApi(test.TestCase):
user = User(id='1', status='used')
mock_qry_all.return_value = [user]
#Query.all = mock.Mock(return_value=[user])
session.query = mock.Mock(return_value=version_values)
versions = api.version_get_all(self.req.context,
filters=filters_value,
@ -768,6 +768,110 @@ class TestSqlalchemyApi(test.TestCase):
sort_dir=sort_dir_value)
self.assertEqual(version_values['id'], versions[0]['id'])
@mock.patch("oslo_db.sqlalchemy.session.Query.all")
@mock.patch('daisy.db.sqlalchemy.api.get_session')
@mock.patch('daisy.db.sqlalchemy.api._version_get')
def test_list_host_patch_history(self, mock_version_get,
mock_do_sesison,
mock_qry_all):
def mock_sesison(*args, **kwargs):
return FakeSession()
class User(object):
def __init__(self, version_id, name):
self.version_id = version_id
self.name = name
def to_dict(self):
return {'version_id': self.version_id}
filters_value = {
'deleted': False,
'host_id': u'a0ed9c30-afd3-4bba'}
limit_value = 25
sort_key_value = ['created_at']
sort_dir_value = ['desc']
mock_do_sesison.side_effect = mock_sesison
user = User(version_id='1', name='test')
mock_qry_all.return_value = [user]
mock_version_get.return_value = user
history = api.list_host_patch_history(self.req.context,
filters=filters_value,
limit=limit_value,
sort_key=sort_key_value,
sort_dir=sort_dir_value)
self.assertEqual('1', history[0]['version_id'])
@mock.patch("oslo_db.sqlalchemy.session.Query.one")
@mock.patch('daisy.db.sqlalchemy.api.get_session')
def test__patch_history_get(self, mock_do_sesison, mock_qry_one):
def mock_sesison(*args, **kwargs):
return FakeSession()
id = "1"
mock_qry_one.return_value = {}
mock_do_sesison.side_effect = mock_sesison
history = api._patch_history_get(self.req.context, id,
force_show_deleted=True)
self.assertEqual({}, history)
@mock.patch('daisy.db.sqlalchemy.api._patch_history_get')
def test_patch_history_get(self, mock_patch_get):
mock_patch_get.return_value = '1'
history = api._patch_history_get(self.req.context, id,
force_show_deleted=True)
self.assertEqual('1', history)
@mock.patch('daisy.db.sqlalchemy.api.get_session')
@mock.patch('daisy.db.sqlalchemy.api._patch_history_get')
@mock.patch('daisy.db.sqlalchemy.models.HostPatchHistory.save')
@mock.patch('daisy.db.sqlalchemy.models.HostPatchHistory.update')
@mock.patch('daisy.db.sqlalchemy.api._update_values')
def test_host_patch_history_update_with_id(self, mock_values,
mock_update,
mock_save,
mock_patch_get,
mock_do_sesison):
def mock_sesison(*args, **kwargs):
return FakeSession()
mock_values.return_value = None
mock_do_sesison.side_effect = mock_sesison
mock_save.return_value = None
mock_update.return_value = {'host_id': '2', 'patch_name': '3'}
mock_patch_get.return_value = models.HostPatchHistory()
patch_history_id = '123'
values = {'patch_name': '3', 'host_id': '2'}
api._host_patch_history_update(self.req.context,
values, patch_history_id)
self.assertTrue(mock_save.called)
@mock.patch('daisy.db.sqlalchemy.api.get_session')
@mock.patch('daisy.db.sqlalchemy.api._patch_history_get')
@mock.patch('daisy.db.sqlalchemy.models.HostPatchHistory.save')
def test_host_patch_history_update_with_no_id(self, mock_save,
mock_patch_get,
mock_do_sesison):
def mock_sesison(*args, **kwargs):
return FakeSession()
mock_do_sesison.side_effect = mock_sesison
mock_save.return_value = None
mock_patch_get.return_value = None
patch_history_id = ''
values = {'patch_name': 'test2', 'host_id': '123'}
api._host_patch_history_update(self.req.context,
values, patch_history_id)
self.assertTrue(mock_save.called)
@mock.patch('daisy.db.sqlalchemy.api._host_patch_history_update')
def test_add_host_patch_history(self, mock_history_update):
values = {'patch_name': 'test2', 'host_id': '123'}
mock_history_update.return_value = None
api.add_host_patch_history(self.req.context,
values)
self.assertTrue(mock_history_update.called)
def test_get_host_interface_vf_info(self):
self.assertRaises(exception.NotFound,
api._get_host_interface_vf_info,

View File

@ -0,0 +1,36 @@
import mock
from daisy import test
import webob
from daisy.context import RequestContext
from daisy.common import exception
from daisy.registry.api.v1 import version_patchs
def fake_version_patch_add(host_id):
if host_id == 'd134fa48-a':
return exception.NotFound
elif host_id == 'd134fa48-b':
return exception.Forbidden
elif host_id == 'd134fa48-c':
return exception.Invalid
class TestVersionpatchs(test.TestCase):
def setUp(self):
super(TestVersionpatchs, self).setUp()
self.controller = version_patchs.Controller()
@mock.patch('daisy.db.sqlalchemy.api.add_host_patch_history')
def test_add_host_patch_history(self, mock_add_patch_history):
self.req = webob.Request.blank('/')
self.req.context = RequestContext(is_admin=True, user='fake user',
tenant='fake tenant')
host_id = 'd04cfa48'
patch_name = 'd134fa'
body = {"patch_history": {'host_id': host_id,
'patch_name': patch_name}}
mock_add_patch_history.return_value = {'host_id': host_id,
'patch_name': patch_name}
actual_data = self.controller.add_host_patch_history(self.req, body)
self.assertEqual(body, actual_data)

View File

@ -2574,6 +2574,61 @@ def do_version_patch_add(dc, args):
_daisy_show(version)
@utils.arg('--host-id', metavar='<HOST_ID>',
help='name of version.')
@utils.arg('--version-id', metavar='<VERSION>',
help='version id')
@utils.arg('--type', metavar='<TYPE>',
help='type is tecs,vplat.....')
@utils.arg('--patch-name', metavar='<PATCH>',
help='patch name')
def do_host_patch_history_add(dc, args):
"""Add a host patch history."""
fields = dict(filter(lambda x: x[1] is not None, vars(args).items()))
# Filter out values we can't use
CREATE_PARAMS = daisyclient.v1.version_patchs.CREATE_PARAMS
fields = dict(filter(lambda x: x[0] in CREATE_PARAMS, fields.items()))
patch_history = dc.version_patchs.add_host_patch_history(**fields)
_daisy_show(patch_history)
@utils.arg('--host-id', metavar='<HOST>',
help='Filter patch history with host id.')
@utils.arg('--type', metavar='<type>',
help='Filter by type.')
@utils.arg('--version-id', metavar='<version>',
help='Filter by version number.')
@utils.arg('--page-size', metavar='<SIZE>', default=None, type=int,
help='Number to request in each paginated request.')
@utils.arg('--sort-key', default='name',
choices=daisyclient.v1.versions.SORT_KEY_VALUES,
help='Sort version list by specified field.')
@utils.arg('--sort-dir', default='asc',
choices=daisyclient.v1.versions.SORT_DIR_VALUES,
help='Sort version list in specified direction.')
def do_patch_history_list(dc, args):
"""List hosts you can access."""
filter_keys = ['host_id', 'type', 'version_id']
filter_items = [(key, getattr(args, key)) for key in filter_keys]
filters = dict([item for item in filter_items if item[1] is not None])
kwargs = {'filters': filters}
if args.page_size is not None:
kwargs['page_size'] = args.page_size
kwargs['sort_key'] = args.sort_key
kwargs['sort_dir'] = args.sort_dir
versions = dc.version_patchs.list_host_patch_history(**kwargs)
columns = ['ID', 'HOST_ID', 'TYPE', 'VERSION_ID', 'VERSION_NAME',
'PATCH_NAME']
utils.print_list(versions, columns)
@utils.arg('id', metavar='<ID>',
help='ID of version patch.')
@utils.arg('--name', metavar='<NAME>',

View File

@ -14,17 +14,16 @@
# under the License.
import copy
from oslo_utils import encodeutils
from oslo_utils import strutils
import six
import six.moves.urllib.parse as urlparse
from daisyclient.common import utils
from daisyclient.openstack.common.apiclient import base
CREATE_PARAMS = ('id', 'name', 'description', 'version_id',
'size', 'checksum', 'status')
'size', 'checksum', 'status', 'host_id',
'patch_name', 'type')
DEFAULT_PAGE_SIZE = 200
@ -54,6 +53,16 @@ class VersionPatch(base.Resource):
class VersionPatchManager(base.ManagerWithFind):
resource_class = VersionPatch
def _list(self, url, response_key, obj_class=None, body=None):
resp, body = self.client.get(url)
if obj_class is None:
obj_class = self.resource_class
data = body[response_key]
return ([obj_class(self, res, loaded=True) for res in data if res],
resp)
def _version_meta_from_headers(self, headers):
meta = {'properties': {}}
safe_decode = encodeutils.safe_decode
@ -130,8 +139,8 @@ class VersionPatchManager(base.ManagerWithFind):
if sort_dir in SORT_DIR_VALUES:
params['sort_dir'] = sort_dir
else:
raise ValueError('sort_dir must be one of the following: %s.'
% ', '.join(SORT_DIR_VALUES))
raise ValueError('sort_dir must be one of the following:'
' %s.' % ', '.join(SORT_DIR_VALUES))
filters = parameters.get('filters', {})
params.update(filters)
@ -200,3 +209,90 @@ class VersionPatchManager(base.ManagerWithFind):
def list(self, **kwargs):
return
def list_host_patch_history(self, **kwargs):
absolute_limit = kwargs.get('limit')
page_size = kwargs.get('page_size', DEFAULT_PAGE_SIZE)
def paginate(qp, return_request_id=None):
for param, value in six.iteritems(qp):
if isinstance(value, six.string_types):
# Note(flaper87) Url encoding should
# be moved inside http utils, at least
# shouldn't be here.
#
# Making sure all params are str before
# trying to encode them
qp[param] = encodeutils.safe_decode(value)
url = '/v1/patch_history/list?%s' % urlparse.urlencode(qp)
patchs_history, resp = self._list(url, "patch_history")
if return_request_id is not None:
return_request_id.append(resp.headers.get(OS_REQ_ID_HDR, None))
for patch_history in patchs_history:
yield patch_history
return_request_id = kwargs.get('return_req_id', None)
params = self._build_params(kwargs)
seen = 0
while True:
seen_last_page = 0
filtered = 0
for template_func in paginate(params, return_request_id):
last_template_func = template_func.id
if (absolute_limit is not None and
seen + seen_last_page >= absolute_limit):
# Note(kragniz): we've seen enough images
return
else:
seen_last_page += 1
yield template_func
seen += seen_last_page
if seen_last_page + filtered == 0:
# Note(kragniz): we didn't get any hosts in the last page
return
if absolute_limit is not None and seen >= absolute_limit:
# Note(kragniz): reached the limit of hosts to return
return
if page_size and seen_last_page + filtered < page_size:
# Note(kragniz): we've reached the last page of the hosts
return
# Note(kragniz): there are more hosts to come
params['marker'] = last_template_func
seen_last_page = 0
def add_host_patch_history(self, **kwargs):
"""Add a version
TODO(bcwaldon): document accepted params
"""
fields = {}
for field in kwargs:
if field in CREATE_PARAMS:
fields[field] = kwargs[field]
elif field == 'return_req_id':
continue
else:
msg = 'create() got an unexpected keyword argument \'%s\''
raise TypeError(msg % field)
hdrs = self._version_meta_to_headers(fields)
resp, body = self.client.post('/v1/patch_history',
headers=None,
data=hdrs)
return_request_id = kwargs.get('return_req_id', None)
if return_request_id is not None:
return_request_id.append(resp.headers.get(OS_REQ_ID_HDR, None))
return VersionPatch(self, self._format_version_meta_for_user(
body['patch_history']))