From 82042ac2766b892d561836cd312656940522734a Mon Sep 17 00:00:00 2001 From: ivan-zhu Date: Wed, 21 Nov 2012 15:37:52 +0800 Subject: [PATCH] Add agent build API support for list/create/delete/modify agent build This adds an extension that provides REST API for list/create/delete/ modify agent build. The interface is accessed via GET /v2/{tenant_id}/os-agents PUT /v2/{tenant_id}/os-agents/id POST /v2/{tenant_id}/os-agents DELETE /v2/{tenant_id}/os-agents And this patch also create tests to get agent build API Samples. DocImpact Implements one workitem of blueprint apis-for-nova-manage The agent is talking about guest agent.The host can use this for things like accessing files on the disk, configuring networking, or running other applications/scripts in the guest while it is running. Typically this uses some hypervisor-specific transport to avoid being dependent on a working network configuration. Xen, VMware, and VirtualBox have guest agents,although the Xen driver is the only one with an implementation for managing them in openstack. KVM doesn't really have a concept of a guest agent (although one could be written). You can find the design of agent update in this link: http://wiki.openstack.org/AgentUpdate and find the code in nova.virt.xenapi.vmops.VMOps._boot_new_instance. In this design We need update agent in guest from host, so we need some interfaces to update the agent info in host. You can find more information about the design of the GuestAgent in the following link: http://wiki.openstack.org/GuestAgent http://wiki.openstack.org/GuestAgentXenStoreCommunication DocImpact Change-Id: I5830388a894efce5b13680fc6916e0cd81a16624 --- .../all_extensions/extensions-get-resp.json | 8 + .../all_extensions/extensions-get-resp.xml | 3 + doc/api_samples/os-agents/agent-post-req.json | 10 + doc/api_samples/os-agents/agent-post-req.xml | 9 + .../os-agents/agent-post-resp.json | 11 ++ doc/api_samples/os-agents/agent-post-resp.xml | 10 + .../os-agents/agent-update-put-req.json | 7 + .../os-agents/agent-update-put-req.xml | 6 + .../os-agents/agent-update-put-resp.json | 8 + .../os-agents/agent-update-put-resp.xml | 7 + .../os-agents/agents-get-resp.json | 13 ++ doc/api_samples/os-agents/agents-get-resp.xml | 4 + etc/nova/policy.json | 1 + nova/api/openstack/compute/contrib/agents.py | 171 ++++++++++++++++ nova/db/api.py | 4 +- nova/db/sqlalchemy/api.py | 25 ++- nova/exception.py | 4 + .../openstack/compute/contrib/test_agents.py | 185 ++++++++++++++++++ .../api/openstack/compute/test_extensions.py | 1 + nova/tests/fake_policy.py | 1 + .../extensions-get-resp.json.tpl | 8 + .../extensions-get-resp.xml.tpl | 3 + .../os-agents/agent-post-req.json.tpl | 10 + .../os-agents/agent-post-req.xml.tpl | 9 + .../os-agents/agent-post-resp.json.tpl | 12 ++ .../os-agents/agent-post-resp.xml.tpl | 10 + .../os-agents/agent-update-put-req.json.tpl | 7 + .../os-agents/agent-update-put-req.xml.tpl | 6 + .../os-agents/agent-update-put-resp.json.tpl | 8 + .../os-agents/agent-update-put-resp.xml.tpl | 7 + .../os-agents/agents-get-resp.json.tpl | 13 ++ .../os-agents/agents-get-resp.xml.tpl | 4 + nova/tests/integrated/test_api_samples.py | 104 ++++++++++ 33 files changed, 679 insertions(+), 10 deletions(-) create mode 100644 doc/api_samples/os-agents/agent-post-req.json create mode 100644 doc/api_samples/os-agents/agent-post-req.xml create mode 100644 doc/api_samples/os-agents/agent-post-resp.json create mode 100644 doc/api_samples/os-agents/agent-post-resp.xml create mode 100644 doc/api_samples/os-agents/agent-update-put-req.json create mode 100644 doc/api_samples/os-agents/agent-update-put-req.xml create mode 100644 doc/api_samples/os-agents/agent-update-put-resp.json create mode 100644 doc/api_samples/os-agents/agent-update-put-resp.xml create mode 100644 doc/api_samples/os-agents/agents-get-resp.json create mode 100644 doc/api_samples/os-agents/agents-get-resp.xml create mode 100644 nova/api/openstack/compute/contrib/agents.py create mode 100644 nova/tests/api/openstack/compute/contrib/test_agents.py create mode 100644 nova/tests/integrated/api_samples/os-agents/agent-post-req.json.tpl create mode 100644 nova/tests/integrated/api_samples/os-agents/agent-post-req.xml.tpl create mode 100644 nova/tests/integrated/api_samples/os-agents/agent-post-resp.json.tpl create mode 100644 nova/tests/integrated/api_samples/os-agents/agent-post-resp.xml.tpl create mode 100644 nova/tests/integrated/api_samples/os-agents/agent-update-put-req.json.tpl create mode 100644 nova/tests/integrated/api_samples/os-agents/agent-update-put-req.xml.tpl create mode 100644 nova/tests/integrated/api_samples/os-agents/agent-update-put-resp.json.tpl create mode 100644 nova/tests/integrated/api_samples/os-agents/agent-update-put-resp.xml.tpl create mode 100644 nova/tests/integrated/api_samples/os-agents/agents-get-resp.json.tpl create mode 100644 nova/tests/integrated/api_samples/os-agents/agents-get-resp.xml.tpl diff --git a/doc/api_samples/all_extensions/extensions-get-resp.json b/doc/api_samples/all_extensions/extensions-get-resp.json index 399d937a7667..cf3d66a761bb 100644 --- a/doc/api_samples/all_extensions/extensions-get-resp.json +++ b/doc/api_samples/all_extensions/extensions-get-resp.json @@ -64,6 +64,14 @@ "namespace": "http://docs.openstack.org/compute/ext/admin-actions/api/v1.1", "updated": "2011-09-20T00:00:00+00:00" }, + { + "alias": "os-agents", + "description": "Agents support", + "links": [], + "name": "Agents", + "namespace": "http://docs.openstack.org/compute/ext/agents/api/v2", + "updated": "2012-10-28T00:00:00-00:00" + }, { "alias": "os-aggregates", "description": "Admin-only aggregate administration", diff --git a/doc/api_samples/all_extensions/extensions-get-resp.xml b/doc/api_samples/all_extensions/extensions-get-resp.xml index e4d3b8cc3106..44816c09cb1e 100644 --- a/doc/api_samples/all_extensions/extensions-get-resp.xml +++ b/doc/api_samples/all_extensions/extensions-get-resp.xml @@ -28,6 +28,9 @@ resetNetwork, injectNetworkInfo, lock, unlock, createBackup + + Agents support + Admin-only aggregate administration diff --git a/doc/api_samples/os-agents/agent-post-req.json b/doc/api_samples/os-agents/agent-post-req.json new file mode 100644 index 000000000000..217993b17f72 --- /dev/null +++ b/doc/api_samples/os-agents/agent-post-req.json @@ -0,0 +1,10 @@ +{ + "agent": { + "hypervisor": "hypervisor", + "os": "os", + "architecture": "x86", + "version": "8.0", + "md5hash": "add6bb58e139be103324d04d82d8f545", + "url": "xxxxxxxxxxxx" + } +} \ No newline at end of file diff --git a/doc/api_samples/os-agents/agent-post-req.xml b/doc/api_samples/os-agents/agent-post-req.xml new file mode 100644 index 000000000000..be93e97ce432 --- /dev/null +++ b/doc/api_samples/os-agents/agent-post-req.xml @@ -0,0 +1,9 @@ + + + hypervisor + os + x86 + 8.0 + add6bb58e139be103324d04d82d8f545 + xxxxxxxxxxxx + \ No newline at end of file diff --git a/doc/api_samples/os-agents/agent-post-resp.json b/doc/api_samples/os-agents/agent-post-resp.json new file mode 100644 index 000000000000..418d11f5042f --- /dev/null +++ b/doc/api_samples/os-agents/agent-post-resp.json @@ -0,0 +1,11 @@ +{ + "agent": { + "agent_id": "1", + "architecture": "x86", + "hypervisor": "hypervisor", + "md5hash": "add6bb58e139be103324d04d82d8f545", + "os": "os", + "url": "xxxxxxxxxxxx", + "version": "8.0" + } +} \ No newline at end of file diff --git a/doc/api_samples/os-agents/agent-post-resp.xml b/doc/api_samples/os-agents/agent-post-resp.xml new file mode 100644 index 000000000000..79f62b7fb92a --- /dev/null +++ b/doc/api_samples/os-agents/agent-post-resp.xml @@ -0,0 +1,10 @@ + + + xxxxxxxxxxxx + hypervisor + add6bb58e139be103324d04d82d8f545 + 8.0 + x86 + os + 1 + \ No newline at end of file diff --git a/doc/api_samples/os-agents/agent-update-put-req.json b/doc/api_samples/os-agents/agent-update-put-req.json new file mode 100644 index 000000000000..e4eaf535256f --- /dev/null +++ b/doc/api_samples/os-agents/agent-update-put-req.json @@ -0,0 +1,7 @@ +{ + "para": { + "url": "xxx://xxxx/xxx/xxx", + "md5hash": "add6bb58e139be103324d04d82d8f545", + "version": "7.0" + } +} \ No newline at end of file diff --git a/doc/api_samples/os-agents/agent-update-put-req.xml b/doc/api_samples/os-agents/agent-update-put-req.xml new file mode 100644 index 000000000000..f759880c1728 --- /dev/null +++ b/doc/api_samples/os-agents/agent-update-put-req.xml @@ -0,0 +1,6 @@ + + + 7.0 + xxx://xxxx/xxx/xxx + add6bb58e139be103324d04d82d8f545 + \ No newline at end of file diff --git a/doc/api_samples/os-agents/agent-update-put-resp.json b/doc/api_samples/os-agents/agent-update-put-resp.json new file mode 100644 index 000000000000..6b67222c8c23 --- /dev/null +++ b/doc/api_samples/os-agents/agent-update-put-resp.json @@ -0,0 +1,8 @@ +{ + "agent": { + "agent_id": "1", + "md5hash": "add6bb58e139be103324d04d82d8f545", + "url": "xxx://xxxx/xxx/xxx", + "version": "7.0" + } +} \ No newline at end of file diff --git a/doc/api_samples/os-agents/agent-update-put-resp.xml b/doc/api_samples/os-agents/agent-update-put-resp.xml new file mode 100644 index 000000000000..badf2750ea5d --- /dev/null +++ b/doc/api_samples/os-agents/agent-update-put-resp.xml @@ -0,0 +1,7 @@ + + + xxx://xxxx/xxx/xxx + 7.0 + 1 + add6bb58e139be103324d04d82d8f545 + \ No newline at end of file diff --git a/doc/api_samples/os-agents/agents-get-resp.json b/doc/api_samples/os-agents/agents-get-resp.json new file mode 100644 index 000000000000..36eac4ced7ba --- /dev/null +++ b/doc/api_samples/os-agents/agents-get-resp.json @@ -0,0 +1,13 @@ +{ + "agents": [ + { + "agent_id": "1", + "architecture": "x86", + "hypervisor": "hypervisor", + "md5hash": "add6bb58e139be103324d04d82d8f545", + "os": "os", + "url": "xxxxxxxxxxxx", + "version": "8.0" + } + ] +} \ No newline at end of file diff --git a/doc/api_samples/os-agents/agents-get-resp.xml b/doc/api_samples/os-agents/agents-get-resp.xml new file mode 100644 index 000000000000..4194f62c965a --- /dev/null +++ b/doc/api_samples/os-agents/agents-get-resp.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/etc/nova/policy.json b/etc/nova/policy.json index 778203e756fc..942b74f66522 100644 --- a/etc/nova/policy.json +++ b/etc/nova/policy.json @@ -28,6 +28,7 @@ "compute_extension:admin_actions:resetState": "rule:admin_api", "compute_extension:admin_actions:migrate": "rule:admin_api", "compute_extension:aggregates": "rule:admin_api", + "compute_extension:agents": "rule:admin_api", "compute_extension:certificates": "", "compute_extension:cloudpipe": "rule:admin_api", "compute_extension:cloudpipe_update": "rule:admin_api", diff --git a/nova/api/openstack/compute/contrib/agents.py b/nova/api/openstack/compute/contrib/agents.py new file mode 100644 index 000000000000..218f06b52324 --- /dev/null +++ b/nova/api/openstack/compute/contrib/agents.py @@ -0,0 +1,171 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2012 IBM +# 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. + + +import webob.exc + +from nova.api.openstack import extensions +from nova.api.openstack import wsgi +from nova.api.openstack import xmlutil +from nova import db +from nova import exception +from nova.openstack.common import log as logging + + +LOG = logging.getLogger(__name__) +authorize = extensions.extension_authorizer('compute', 'agents') + + +class AgentsIndexTemplate(xmlutil.TemplateBuilder): + def construct(self): + root = xmlutil.TemplateElement('agents') + elem = xmlutil.SubTemplateElement(root, 'agent', selector='agents') + elem.set('hypervisor') + elem.set('os') + elem.set('architecture') + elem.set('version') + elem.set('md5hash') + elem.set('agent_id') + elem.set('url') + + return xmlutil.MasterTemplate(root, 1) + + +class AgentController(object): + """ + The agent is talking about guest agent.The host can use this for + things like accessing files on the disk, configuring networking, + or running other applications/scripts in the guest while it is + running. Typically this uses some hypervisor-specific transport + to avoid being dependent on a working network configuration. + Xen, VMware, and VirtualBox have guest agents,although the Xen + driver is the only one with an implementation for managing them + in openstack. KVM doesn't really have a concept of a guest agent + (although one could be written). + + You can find the design of agent update in this link: + http://wiki.openstack.org/AgentUpdate + and find the code in nova.virt.xenapi.vmops.VMOps._boot_new_instance. + In this design We need update agent in guest from host, so we need + some interfaces to update the agent info in host. + + You can find more information about the design of the GuestAgent in + the following link: + http://wiki.openstack.org/GuestAgent + http://wiki.openstack.org/GuestAgentXenStoreCommunication + """ + @wsgi.serializers(xml=AgentsIndexTemplate) + def index(self, req): + """ + Return a list of all agent builds. Filter by hypervisor. + """ + context = req.environ['nova.context'] + authorize(context) + hypervisor = None + agents = [] + if 'hypervisor' in req.GET: + hypervisor = req.GET['hypervisor'] + + for agent_build in db.agent_build_get_all(context, hypervisor): + agents.append({'hypervisor': agent_build.hypervisor, + 'os': agent_build.os, + 'architecture': agent_build.architecture, + 'version': agent_build.version, + 'md5hash': agent_build.md5hash, + 'agent_id': agent_build.id, + 'url': agent_build.url}) + + return {'agents': agents} + + def update(self, req, id, body): + """Update an existing agent build.""" + context = req.environ['nova.context'] + authorize(context) + + try: + para = body['para'] + url = para['url'] + md5hash = para['md5hash'] + version = para['version'] + except (TypeError, KeyError): + raise webob.exc.HTTPUnprocessableEntity() + + try: + db.agent_build_update(context, id, + {'version': version, + 'url': url, + 'md5hash': md5hash}) + except exception.AgentBuildNotFound as ex: + raise webob.exc.HTTPNotFound(explanation=str(ex)) + + return {"agent": {'agent_id': id, 'version': version, + 'url': url, 'md5hash': md5hash}} + + def delete(self, req, id): + """Deletes an existing agent build.""" + context = req.environ['nova.context'] + authorize(context) + + try: + db.agent_build_destroy(context, id) + except exception.AgentBuildNotFound as ex: + raise webob.exc.HTTPNotFound(explanation=str(ex)) + + def create(self, req, body): + """Creates a new agent build.""" + context = req.environ['nova.context'] + authorize(context) + + try: + agent = body['agent'] + hypervisor = agent['hypervisor'] + os = agent['os'] + architecture = agent['architecture'] + version = agent['version'] + url = agent['url'] + md5hash = agent['md5hash'] + except (TypeError, KeyError): + raise webob.exc.HTTPUnprocessableEntity() + + try: + agent_build_ref = db.agent_build_create(context, + {'hypervisor': hypervisor, + 'os': os, + 'architecture': architecture, + 'version': version, + 'url': url, + 'md5hash': md5hash}) + agent['agent_id'] = agent_build_ref.id + except Exception as ex: + raise webob.exc.HTTPServerError(str(ex)) + return {'agent': agent} + + +class Agents(extensions.ExtensionDescriptor): + """Agents support""" + + name = "Agents" + alias = "os-agents" + namespace = "http://docs.openstack.org/compute/ext/agents/api/v2" + updated = "2012-10-28T00:00:00-00:00" + + def get_resources(self): + resources = [] + resource = extensions.ResourceExtension('os-agents', + AgentController()) + resources.append(resource) + return resources diff --git a/nova/db/api.py b/nova/db/api.py index ad928f5852e6..cfa6a6487711 100644 --- a/nova/db/api.py +++ b/nova/db/api.py @@ -1372,9 +1372,9 @@ def agent_build_get_by_triple(context, hypervisor, os, architecture): architecture) -def agent_build_get_all(context): +def agent_build_get_all(context, hypervisor=None): """Get all agent builds.""" - return IMPL.agent_build_get_all(context) + return IMPL.agent_build_get_all(context, hypervisor) def agent_build_destroy(context, agent_update_id): diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index c1b6e66dde4d..e8eea7d023a6 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -3968,8 +3968,13 @@ def agent_build_get_by_triple(context, hypervisor, os, architecture, @require_admin_context -def agent_build_get_all(context): - return model_query(context, models.AgentBuild, read_deleted="no").\ +def agent_build_get_all(context, hypervisor=None): + if hypervisor: + return model_query(context, models.AgentBuild, read_deleted="no").\ + filter_by(hypervisor=hypervisor).\ + all() + else: + return model_query(context, models.AgentBuild, read_deleted="no").\ all() @@ -3977,12 +3982,15 @@ def agent_build_get_all(context): def agent_build_destroy(context, agent_build_id): session = get_session() with session.begin(): - model_query(context, models.AgentBuild, session=session, - read_deleted="yes").\ + agent_build_ref = model_query(context, models.AgentBuild, + session=session, read_deleted="yes").\ filter_by(id=agent_build_id).\ - update({'deleted': True, - 'deleted_at': timeutils.utcnow(), - 'updated_at': literal_column('updated_at')}) + first() + if not agent_build_ref: + raise exception.AgentBuildNotFound(id=agent_build_id) + agent_build_ref.update({'deleted': True, + 'deleted_at': timeutils.utcnow(), + 'updated_at': literal_column('updated_at')}) @require_admin_context @@ -3993,7 +4001,8 @@ def agent_build_update(context, agent_build_id, values): session=session, read_deleted="yes").\ filter_by(id=agent_build_id).\ first() - + if not agent_build_ref: + raise exception.AgentBuildNotFound(id=agent_build_id) agent_build_ref.update(values) agent_build_ref.save(session=session) diff --git a/nova/exception.py b/nova/exception.py index 7629db9fe441..7477d9c63819 100644 --- a/nova/exception.py +++ b/nova/exception.py @@ -412,6 +412,10 @@ class NotFound(NovaException): code = 404 +class AgentBuildNotFound(NotFound): + message = _("No agent-build associated with id %(id)s.") + + class VolumeNotFound(NotFound): message = _("Volume %(volume_id)s could not be found.") diff --git a/nova/tests/api/openstack/compute/contrib/test_agents.py b/nova/tests/api/openstack/compute/contrib/test_agents.py new file mode 100644 index 000000000000..60659b3c6aba --- /dev/null +++ b/nova/tests/api/openstack/compute/contrib/test_agents.py @@ -0,0 +1,185 @@ +# Copyright 2012 IBM +# 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 nova.api.openstack.compute.contrib import agents +from nova import context +from nova import db +from nova.db.sqlalchemy import models +from nova import test + +fake_agents_list = [{'hypervisor': 'kvm', 'os': 'win', + 'architecture': 'x86', + 'version': '7.0', + 'url': 'xxx://xxxx/xxx/xxx', + 'md5hash': 'add6bb58e139be103324d04d82d8f545', + 'id': 1}, + {'hypervisor': 'kvm', 'os': 'linux', + 'architecture': 'x86', + 'version': '16.0', + 'url': 'xxx://xxxx/xxx/xxx1', + 'md5hash': 'add6bb58e139be103324d04d82d8f546', + 'id': 2}, + {'hypervisor': 'xen', 'os': 'linux', + 'architecture': 'x86', + 'version': '16.0', + 'url': 'xxx://xxxx/xxx/xxx2', + 'md5hash': 'add6bb58e139be103324d04d82d8f547', + 'id': 3}, + {'hypervisor': 'xen', 'os': 'win', + 'architecture': 'power', + 'version': '7.0', + 'url': 'xxx://xxxx/xxx/xxx3', + 'md5hash': 'add6bb58e139be103324d04d82d8f548', + 'id': 4}, + ] + + +def fake_agent_build_get_all(context, hypervisor): + agent_build_all = [] + for agent in fake_agents_list: + if hypervisor and hypervisor != agent['hypervisor']: + continue + agent_build_ref = models.AgentBuild() + agent_build_ref.update(agent) + agent_build_all.append(agent_build_ref) + return agent_build_all + + +def fake_agent_build_update(context, agent_build_id, values): + pass + + +def fake_agent_build_destroy(context, agent_update_id): + pass + + +def fake_agent_build_create(context, values): + values['id'] = 1 + agent_build_ref = models.AgentBuild() + agent_build_ref.update(values) + return agent_build_ref + + +class FakeRequest(object): + environ = {"nova.context": context.get_admin_context()} + GET = {} + + +class FakeRequestWithHypervisor(object): + environ = {"nova.context": context.get_admin_context()} + GET = {'hypervisor': 'kvm'} + + +class AgentsTest(test.TestCase): + + def setUp(self): + super(AgentsTest, self).setUp() + + self.stubs.Set(db, "agent_build_get_all", + fake_agent_build_get_all) + self.stubs.Set(db, "agent_build_update", + fake_agent_build_update) + self.stubs.Set(db, "agent_build_destroy", + fake_agent_build_destroy) + self.stubs.Set(db, "agent_build_create", + fake_agent_build_create) + self.context = context.get_admin_context() + self.controller = agents.AgentController() + + def tearDown(self): + super(AgentsTest, self).tearDown() + + def test_agents_create(self): + req = FakeRequest() + body = {'agent': {'hypervisor': 'kvm', + 'os': 'win', + 'architecture': 'x86', + 'version': '7.0', + 'url': 'xxx://xxxx/xxx/xxx', + 'md5hash': 'add6bb58e139be103324d04d82d8f545'}} + response = {'agent': {'hypervisor': 'kvm', + 'os': 'win', + 'architecture': 'x86', + 'version': '7.0', + 'url': 'xxx://xxxx/xxx/xxx', + 'md5hash': 'add6bb58e139be103324d04d82d8f545', + 'agent_id': 1}} + res_dict = self.controller.create(req, body) + self.assertEqual(res_dict, response) + + def test_agents_delete(self): + req = FakeRequest() + self.controller.delete(req, 1) + + def test_agents_list(self): + req = FakeRequest() + res_dict = self.controller.index(req) + agents_list = [{'hypervisor': 'kvm', 'os': 'win', + 'architecture': 'x86', + 'version': '7.0', + 'url': 'xxx://xxxx/xxx/xxx', + 'md5hash': 'add6bb58e139be103324d04d82d8f545', + 'agent_id': 1}, + {'hypervisor': 'kvm', 'os': 'linux', + 'architecture': 'x86', + 'version': '16.0', + 'url': 'xxx://xxxx/xxx/xxx1', + 'md5hash': 'add6bb58e139be103324d04d82d8f546', + 'agent_id': 2}, + {'hypervisor': 'xen', 'os': 'linux', + 'architecture': 'x86', + 'version': '16.0', + 'url': 'xxx://xxxx/xxx/xxx2', + 'md5hash': 'add6bb58e139be103324d04d82d8f547', + 'agent_id': 3}, + {'hypervisor': 'xen', 'os': 'win', + 'architecture': 'power', + 'version': '7.0', + 'url': 'xxx://xxxx/xxx/xxx3', + 'md5hash': 'add6bb58e139be103324d04d82d8f548', + 'agent_id': 4}, + ] + self.assertEqual(res_dict, {'agents': agents_list}) + + def test_agents_list_with_hypervisor(self): + req = FakeRequestWithHypervisor() + res_dict = self.controller.index(req) + response = [{'hypervisor': 'kvm', 'os': 'win', + 'architecture': 'x86', + 'version': '7.0', + 'url': 'xxx://xxxx/xxx/xxx', + 'md5hash': 'add6bb58e139be103324d04d82d8f545', + 'agent_id': 1}, + {'hypervisor': 'kvm', 'os': 'linux', + 'architecture': 'x86', + 'version': '16.0', + 'url': 'xxx://xxxx/xxx/xxx1', + 'md5hash': 'add6bb58e139be103324d04d82d8f546', + 'agent_id': 2}, + ] + self.assertEqual(res_dict, {'agents': response}) + + def test_agents_update(self): + req = FakeRequest() + body = {'para': {'version': '7.0', + 'url': 'xxx://xxxx/xxx/xxx', + 'md5hash': 'add6bb58e139be103324d04d82d8f545'}} + response = {'agent': {'agent_id': 1, + 'version': '7.0', + 'url': 'xxx://xxxx/xxx/xxx', + 'md5hash': 'add6bb58e139be103324d04d82d8f545'}} + res_dict = self.controller.update(req, 1, body) + self.assertEqual(res_dict, response) diff --git a/nova/tests/api/openstack/compute/test_extensions.py b/nova/tests/api/openstack/compute/test_extensions.py index eab55f95e0a4..66dac3febe8a 100644 --- a/nova/tests/api/openstack/compute/test_extensions.py +++ b/nova/tests/api/openstack/compute/test_extensions.py @@ -158,6 +158,7 @@ class ExtensionControllerTest(ExtensionTestCase): "AdminActions", "Aggregates", "AvailabilityZone", + "Agents", "Certificates", "Cloudpipe", "CloudpipeUpdate", diff --git a/nova/tests/fake_policy.py b/nova/tests/fake_policy.py index b3ae0fa177df..7813cddc0d03 100644 --- a/nova/tests/fake_policy.py +++ b/nova/tests/fake_policy.py @@ -103,6 +103,7 @@ policy_data = """ "compute_extension:admin_actions:resetState": "", "compute_extension:admin_actions:migrate": "", "compute_extension:aggregates": "", + "compute_extension:agents": "", "compute_extension:certificates": "", "compute_extension:cloudpipe": "", "compute_extension:cloudpipe_update": "", diff --git a/nova/tests/integrated/api_samples/all_extensions/extensions-get-resp.json.tpl b/nova/tests/integrated/api_samples/all_extensions/extensions-get-resp.json.tpl index 65cbb4889731..0b27896fd6a5 100644 --- a/nova/tests/integrated/api_samples/all_extensions/extensions-get-resp.json.tpl +++ b/nova/tests/integrated/api_samples/all_extensions/extensions-get-resp.json.tpl @@ -72,6 +72,14 @@ "namespace": "http://docs.openstack.org/compute/ext/aggregates/api/v1.1", "updated": "%(timestamp)s" }, + { + "alias": "os-agents", + "description": "%(text)s", + "links": [], + "name": "Agents", + "namespace": "http://docs.openstack.org/compute/ext/agents/api/v2", + "updated": "%(timestamp)s" + }, { "alias": "os-availability-zone", "description": "%(text)s", diff --git a/nova/tests/integrated/api_samples/all_extensions/extensions-get-resp.xml.tpl b/nova/tests/integrated/api_samples/all_extensions/extensions-get-resp.xml.tpl index bdef0266cfea..fec850997e1f 100644 --- a/nova/tests/integrated/api_samples/all_extensions/extensions-get-resp.xml.tpl +++ b/nova/tests/integrated/api_samples/all_extensions/extensions-get-resp.xml.tpl @@ -30,6 +30,9 @@ %(text)s + + %(text)s + %(text)s diff --git a/nova/tests/integrated/api_samples/os-agents/agent-post-req.json.tpl b/nova/tests/integrated/api_samples/os-agents/agent-post-req.json.tpl new file mode 100644 index 000000000000..6dbd2f17cbb5 --- /dev/null +++ b/nova/tests/integrated/api_samples/os-agents/agent-post-req.json.tpl @@ -0,0 +1,10 @@ +{ + "agent": { + "hypervisor": "%(hypervisor)s", + "os": "%(os)s", + "architecture": "%(architecture)s", + "version": "%(version)s", + "md5hash": "%(md5hash)s", + "url": "%(url)s" + } +} diff --git a/nova/tests/integrated/api_samples/os-agents/agent-post-req.xml.tpl b/nova/tests/integrated/api_samples/os-agents/agent-post-req.xml.tpl new file mode 100644 index 000000000000..5c777749a21d --- /dev/null +++ b/nova/tests/integrated/api_samples/os-agents/agent-post-req.xml.tpl @@ -0,0 +1,9 @@ + + + %(hypervisor)s + %(os)s + %(architecture)s + %(version)s + %(md5hash)s + %(url)s + diff --git a/nova/tests/integrated/api_samples/os-agents/agent-post-resp.json.tpl b/nova/tests/integrated/api_samples/os-agents/agent-post-resp.json.tpl new file mode 100644 index 000000000000..abe83564f72b --- /dev/null +++ b/nova/tests/integrated/api_samples/os-agents/agent-post-resp.json.tpl @@ -0,0 +1,12 @@ +{ + "agent": { + "hypervisor": "%(hypervisor)s", + "os": "%(os)s", + "architecture": "%(architecture)s", + "version": "%(version)s", + "md5hash": "%(md5hash)s", + "url": "%(url)s", + "agent_id": "%(agent_id)d" + } +} + diff --git a/nova/tests/integrated/api_samples/os-agents/agent-post-resp.xml.tpl b/nova/tests/integrated/api_samples/os-agents/agent-post-resp.xml.tpl new file mode 100644 index 000000000000..ecf97b91e958 --- /dev/null +++ b/nova/tests/integrated/api_samples/os-agents/agent-post-resp.xml.tpl @@ -0,0 +1,10 @@ + + + %(url)s + %(hypervisor)s + %(md5hash)s + %(version)s + %(architecture)s + %(os)s + %(agent_id)d + diff --git a/nova/tests/integrated/api_samples/os-agents/agent-update-put-req.json.tpl b/nova/tests/integrated/api_samples/os-agents/agent-update-put-req.json.tpl new file mode 100644 index 000000000000..d447350e0dfb --- /dev/null +++ b/nova/tests/integrated/api_samples/os-agents/agent-update-put-req.json.tpl @@ -0,0 +1,7 @@ +{ + "para": { + "url": "%(url)s", + "md5hash": "%(md5hash)s", + "version": "%(version)s" + } +} diff --git a/nova/tests/integrated/api_samples/os-agents/agent-update-put-req.xml.tpl b/nova/tests/integrated/api_samples/os-agents/agent-update-put-req.xml.tpl new file mode 100644 index 000000000000..19751dc80720 --- /dev/null +++ b/nova/tests/integrated/api_samples/os-agents/agent-update-put-req.xml.tpl @@ -0,0 +1,6 @@ + + + %(version)s + %(url)s + %(md5hash)s + diff --git a/nova/tests/integrated/api_samples/os-agents/agent-update-put-resp.json.tpl b/nova/tests/integrated/api_samples/os-agents/agent-update-put-resp.json.tpl new file mode 100644 index 000000000000..110e52cd3317 --- /dev/null +++ b/nova/tests/integrated/api_samples/os-agents/agent-update-put-resp.json.tpl @@ -0,0 +1,8 @@ +{ + "agent": { + "agent_id": "%(agent_id)d", + "url": "%(url)s", + "md5hash": "%(md5hash)s", + "version": "%(version)s" + } +} diff --git a/nova/tests/integrated/api_samples/os-agents/agent-update-put-resp.xml.tpl b/nova/tests/integrated/api_samples/os-agents/agent-update-put-resp.xml.tpl new file mode 100644 index 000000000000..2c9e50572cf7 --- /dev/null +++ b/nova/tests/integrated/api_samples/os-agents/agent-update-put-resp.xml.tpl @@ -0,0 +1,7 @@ + + + %(agent_id)d + %(version)s + %(url)s + %(md5hash)s + diff --git a/nova/tests/integrated/api_samples/os-agents/agents-get-resp.json.tpl b/nova/tests/integrated/api_samples/os-agents/agents-get-resp.json.tpl new file mode 100644 index 000000000000..dac1f76ffb6f --- /dev/null +++ b/nova/tests/integrated/api_samples/os-agents/agents-get-resp.json.tpl @@ -0,0 +1,13 @@ +{ + "agents": [ + { + "hypervisor": "%(hypervisor)s", + "os": "%(os)s", + "architecture": "%(architecture)s", + "version": "%(version)s", + "md5hash": "%(md5hash)s", + "url": "%(url)s", + "agent_id": "%(agent_id)d" + } + ] +} diff --git a/nova/tests/integrated/api_samples/os-agents/agents-get-resp.xml.tpl b/nova/tests/integrated/api_samples/os-agents/agents-get-resp.xml.tpl new file mode 100644 index 000000000000..fbbbdad28850 --- /dev/null +++ b/nova/tests/integrated/api_samples/os-agents/agents-get-resp.xml.tpl @@ -0,0 +1,4 @@ + + + + diff --git a/nova/tests/integrated/test_api_samples.py b/nova/tests/integrated/test_api_samples.py index 4936ff2cbc45..9396583829b4 100644 --- a/nova/tests/integrated/test_api_samples.py +++ b/nova/tests/integrated/test_api_samples.py @@ -26,6 +26,7 @@ from nova.cloudpipe.pipelib import CloudPipe from nova.compute import api from nova import context from nova import db +from nova.db.sqlalchemy import models from nova.network.manager import NetworkManager from nova.openstack.common import cfg from nova.openstack.common import importutils @@ -1284,6 +1285,109 @@ class CloudPipeUpdateXmlTest(CloudPipeUpdateJsonTest): ctype = "xml" +class AgentsJsonTest(ApiSampleTestBase): + extension_name = "nova.api.openstack.compute.contrib.agents.Agents" + + def _get_flags(self): + f = super(AgentsJsonTest, self)._get_flags() + f['osapi_compute_extension'] = CONF.osapi_compute_extension[:] + return f + + def setUp(self): + super(AgentsJsonTest, self).setUp() + + fake_agents_list = [{'url': 'xxxxxxxxxxxx', + 'hypervisor': 'hypervisor', + 'architecture': 'x86', + 'os': 'os', + 'version': '8.0', + 'md5hash': 'add6bb58e139be103324d04d82d8f545', + 'id': '1'}] + + def fake_agent_build_create(context, values): + values['id'] = '1' + agent_build_ref = models.AgentBuild() + agent_build_ref.update(values) + return agent_build_ref + + def fake_agent_build_get_all(context, hypervisor): + agent_build_all = [] + for agent in fake_agents_list: + if hypervisor and hypervisor != agent['hypervisor']: + continue + agent_build_ref = models.AgentBuild() + agent_build_ref.update(agent) + agent_build_all.append(agent_build_ref) + return agent_build_all + + def fake_agent_build_update(context, agent_build_id, values): + pass + + def fake_agent_build_destroy(context, agent_update_id): + pass + + self.stubs.Set(db, "agent_build_create", + fake_agent_build_create) + self.stubs.Set(db, "agent_build_get_all", + fake_agent_build_get_all) + self.stubs.Set(db, "agent_build_update", + fake_agent_build_update) + self.stubs.Set(db, "agent_build_destroy", + fake_agent_build_destroy) + + def test_agent_create(self): + """Creates a new agent build.""" + project = {'url': 'xxxxxxxxxxxx', + 'hypervisor': 'hypervisor', + 'architecture': 'x86', + 'os': 'os', + 'version': '8.0', + 'md5hash': 'add6bb58e139be103324d04d82d8f545' + } + response = self._do_post('os-agents', 'agent-post-req', + project) + self.assertEqual(response.status, 200) + project['agent_id'] = 1 + self._verify_response('agent-post-resp', project, response) + return project + + def test_agent_list(self): + """ Return a list of all agent builds.""" + response = self._do_get('os-agents') + self.assertEqual(response.status, 200) + project = {'url': 'xxxxxxxxxxxx', + 'hypervisor': 'hypervisor', + 'architecture': 'x86', + 'os': 'os', + 'version': '8.0', + 'md5hash': 'add6bb58e139be103324d04d82d8f545', + 'agent_id': 1 + } + return self._verify_response('agents-get-resp', project, response) + + def test_agent_update(self): + """Update an existing agent build.""" + agent_id = 1 + subs = {'version': '7.0', + 'url': 'xxx://xxxx/xxx/xxx', + 'md5hash': 'add6bb58e139be103324d04d82d8f545'} + response = self._do_put('os-agents/%s' % agent_id, + 'agent-update-put-req', subs) + self.assertEqual(response.status, 200) + subs['agent_id'] = 1 + return self._verify_response('agent-update-put-resp', subs, response) + + def test_agent_delete(self): + """Deletes an existing agent build.""" + agent_id = 1 + response = self._do_delete('os-agents/%s' % agent_id) + self.assertEqual(response.status, 200) + + +class AgentsXmlTest(AgentsJsonTest): + ctype = "xml" + + class AggregatesSampleJsonTest(ServersSampleBase): extension_name = "nova.api.openstack.compute.contrib" + \ ".aggregates.Aggregates"