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"