Merge lp:~salvatore-orlando/neutron/quantum-api-alignment into lp:neutron/diablo

Proposed by Salvatore Orlando
Status: Merged
Approved by: Salvatore Orlando
Approved revision: 48
Merged at revision: 51
Proposed branch: lp:~salvatore-orlando/neutron/quantum-api-alignment
Merge into: lp:neutron/diablo
Diff against target: 1449 lines (+501/-259)
21 files modified
etc/quantum.conf (+1/-1)
quantum/api/__init__.py (+9/-3)
quantum/api/api_common.py (+9/-18)
quantum/api/attachments.py (+86/-0)
quantum/api/faults.py (+0/-17)
quantum/api/networks.py (+34/-43)
quantum/api/ports.py (+63/-84)
quantum/api/versions.py (+2/-2)
quantum/api/views/attachments.py (+37/-0)
quantum/api/views/networks.py (+9/-10)
quantum/api/views/ports.py (+6/-14)
quantum/cli.py (+11/-10)
quantum/client.py (+2/-5)
quantum/common/exceptions.py (+0/-10)
quantum/common/wsgi.py (+10/-4)
quantum/db/api.py (+8/-1)
quantum/db/models.py (+2/-1)
quantum/plugins/SamplePlugin.py (+2/-2)
tests/unit/test_api.py (+151/-15)
tests/unit/test_clientlib.py (+1/-1)
tests/unit/testlib_api.py (+58/-18)
To merge this branch: bzr merge lp:~salvatore-orlando/neutron/quantum-api-alignment
Reviewer Review Type Date Requested Status
dan wendlandt Approve
Somik Behera netstack-core Approve
Tyler Smith Approve
Review via email: mp+70788@code.launchpad.net

Description of the change

This is the branch that updates the API code in order to make it 100% compliant with the API specification.

This branch DOES NOT include the status proposal, but includes all feedback received by the community.

API Unit tests have been adapted were necessary(e.g.: attribute names changed)
Client Library Unit tests pass as well.

To post a comment you must log in.
Revision history for this message
dan wendlandt (danwent) wrote :

Hi salvatore, haven't run it yet and only skimmed the test code, but looks good overall.

I do think though that we need to get to the bottom of the "state + status" discussion before pushing this branch, as even this branch had some things that surprised me, particularly that one could not make an attachment to a port in the down state. This seems at odds to my understanding from the email thread that "state" was really akin to "admin state" on a traditional switch. When a switch is "admin down", my understanding is that the common behavior one can still configure the port, its just that no packets will pass.

Also, the spec still seems somewhat unclear on the issue of port "state". For example, the following section, as I interpret it, seems to imply that the state is either "down" or "active" based on whether a VIF is plugged in (i.e., something similar to the operational "link status" we discussed in the last email):

"The VIF will be connected to the network via a logical port. The logical port begins in a “down” state. Logic ports are transient when in the “down” state, by this we mean they don’t have any binding to a physical implementation. The plugin must create this binding as a part of the attachment process. When a VIF is attached to a logical port the plugin creates the binding to the physical hardware required to allow operation of the VIF. This is indicated by the change in port state from “down” to “active”. As a result of the attachment operation, quantum can now knows the capabilities of the VIF as dictated by the physical binding. As a result the logical port may advertise capabilities once it moves to the “active” state."

If you'd prefer to push this code first to avoid merge conflicts and then continue having the discussion that's fine with me, but I want to make sure we clear up this point before finalizing 1.0 (An alternative would be to drop the state/status notion all together for v1.0, and revisit it for a later version).

Revision history for this message
Salvatore Orlando (salvatore-orlando) wrote :

> Hi salvatore, haven't run it yet and only skimmed the test code, but looks
> good overall.
>
> I do think though that we need to get to the bottom of the "state + status"
> discussion before pushing this branch, as even this branch had some things
> that surprised me, particularly that one could not make an attachment to a
> port in the down state. This seems at odds to my understanding from the email
> thread that "state" was really akin to "admin state" on a traditional switch.
> When a switch is "admin down", my understanding is that the common behavior
> one can still configure the port, its just that no packets will pass.
>
> Also, the spec still seems somewhat unclear on the issue of port "state". For
> example, the following section, as I interpret it, seems to imply that the
> state is either "down" or "active" based on whether a VIF is plugged in (i.e.,
> something similar to the operational "link status" we discussed in the last
> email):
>
> "The VIF will be connected to the network via a logical port. The logical port
> begins in a “down” state. Logic ports are transient when in the “down” state,
> by this we mean they don’t have any binding to a physical implementation. The
> plugin must create this binding as a part of the attachment process. When a
> VIF is attached to a logical port the plugin creates the binding to the
> physical hardware required to allow operation of the VIF. This is indicated by
> the change in port state from “down” to “active”. As a result of the
> attachment operation, quantum can now knows the capabilities of the VIF as
> dictated by the physical binding. As a result the logical port may advertise
> capabilities once it moves to the “active” state."
>
> If you'd prefer to push this code first to avoid merge conflicts and then
> continue having the discussion that's fine with me, but I want to make sure we
> clear up this point before finalizing 1.0 (An alternative would be to drop the
> state/status notion all together for v1.0, and revisit it for a later
> version).

I think there are enough comments and questions to drop everything related to "resource state" in any form.
It looks like we all feel the need for some sort of notion of "state", but we are coming at it from rather different angles.
I therefore think it would be better to remove any state concept, including the administrative state for the port, and then resume the discussion post Diablo-4, finalize it at the summit in October, and include the state notion in API v1.1

Revision history for this message
Salvatore Orlando (salvatore-orlando) wrote :

Reverting to WIP to remove all references to resource state.

Revision history for this message
dan wendlandt (danwent) wrote :

One other question for you: as part of nova + quantum integration, I'm prototyping the "admin API" interfaces to communicate information like interface-ownership (for authenticating attachments) and interface-bindings (for knowing where a vif was deployed).

My gut says that we should not hold up the 1.0 version while waiting to include these items, but I wanted to get your thoughts there. We definitely will need to document these eventually though, as there will be other openstack services that want to integrate with Quantum.

review: Needs Information
Revision history for this message
Salvatore Orlando (salvatore-orlando) wrote :

> One other question for you: as part of nova + quantum integration, I'm
> prototyping the "admin API" interfaces to communicate information like
> interface-ownership (for authenticating attachments) and interface-bindings
> (for knowing where a vif was deployed).
>
> My gut says that we should not hold up the 1.0 version while waiting to
> include these items, but I wanted to get your thoughts there. We definitely
> will need to document these eventually though, as there will be other
> openstack services that want to integrate with Quantum.

Good point. I guess the admin API has a different endpoint, as it should be exposed by nova, is that correct?
In that case I'm not entirely sure this should be part of the Quantum API at all (for instance if it going to be an Openstack API extension, why should it be documented in Quantum API). I can't find a blueprint for tracking this piece of work, can you give me a pointer?

My plan to lock down the API for this week is motivated by the fact that we have to lock down features by Aug 25, and then do bug fixing and system testing until diablo release. If you think that we only have two weeks left, and given that code review process usually takes a week, this means that the best way to ensure a timely completion for the API blueprint is to start locking down the spec this week, and then work the week after on the code.

Revision history for this message
dan wendlandt (danwent) wrote :
Download full text (3.2 KiB)

I think this is the right call. We don't necessarily have to wait that long
(or even to stop talking about it at all, but I agree with getting v1.0 out
ASAP).

dan

On Mon, Aug 8, 2011 at 2:47 PM, Salvatore Orlando <
<email address hidden>> wrote:

> > Hi salvatore, haven't run it yet and only skimmed the test code, but
> looks
> > good overall.
> >
> > I do think though that we need to get to the bottom of the "state +
> status"
> > discussion before pushing this branch, as even this branch had some
> things
> > that surprised me, particularly that one could not make an attachment to
> a
> > port in the down state. This seems at odds to my understanding from the
> email
> > thread that "state" was really akin to "admin state" on a traditional
> switch.
> > When a switch is "admin down", my understanding is that the common
> behavior
> > one can still configure the port, its just that no packets will pass.
> >
> > Also, the spec still seems somewhat unclear on the issue of port "state".
> For
> > example, the following section, as I interpret it, seems to imply that
> the
> > state is either "down" or "active" based on whether a VIF is plugged in
> (i.e.,
> > something similar to the operational "link status" we discussed in the
> last
> > email):
> >
> > "The VIF will be connected to the network via a logical port. The logical
> port
> > begins in a “down” state. Logic ports are transient when in the “down”
> state,
> > by this we mean they don’t have any binding to a physical implementation.
> The
> > plugin must create this binding as a part of the attachment process. When
> a
> > VIF is attached to a logical port the plugin creates the binding to the
> > physical hardware required to allow operation of the VIF. This is
> indicated by
> > the change in port state from “down” to “active”. As a result of the
> > attachment operation, quantum can now knows the capabilities of the VIF
> as
> > dictated by the physical binding. As a result the logical port may
> advertise
> > capabilities once it moves to the “active” state."
> >
> > If you'd prefer to push this code first to avoid merge conflicts and then
> > continue having the discussion that's fine with me, but I want to make
> sure we
> > clear up this point before finalizing 1.0 (An alternative would be to
> drop the
> > state/status notion all together for v1.0, and revisit it for a later
> > version).
>
> I think there are enough comments and questions to drop everything related
> to "resource state" in any form.
> It looks like we all feel the need for some sort of notion of "state", but
> we are coming at it from rather different angles.
> I therefore think it would be better to remove any state concept, including
> the administrative state for the port, and then resume the discussion post
> Diablo-4, finalize it at the summit in October, and include the state notion
> in API v1.1
> --
>
> https://code.launchpad.net/~salvatore-orlando/quantum/quantum-api-alignment/+merge/70788
> You are requested to review the proposed merge of
> lp:~salvatore-orlando/quantum/quantum-api-alignment into lp:quantum.
>

--
~~~~~~~~~~~~~~~~~~~~~~~~~~~
Dan Wendlandt
Nicira Networks, Inc.
www...

Read more...

Revision history for this message
dan wendlandt (danwent) wrote :
Download full text (4.0 KiB)

On Mon, Aug 8, 2011 at 3:13 PM, Salvatore Orlando <
<email address hidden>> wrote:

> > One other question for you: as part of nova + quantum integration, I'm
> > prototyping the "admin API" interfaces to communicate information like
> > interface-ownership (for authenticating attachments) and
> interface-bindings
> > (for knowing where a vif was deployed).
> >
> > My gut says that we should not hold up the 1.0 version while waiting to
> > include these items, but I wanted to get your thoughts there. We
> definitely
> > will need to document these eventually though, as there will be other
> > openstack services that want to integrate with Quantum.
>
> Good point. I guess the admin API has a different endpoint, as it should be
> exposed by nova, is that correct?
>

I think one could think of doing it in two ways:

1) exposing an endpoint on nova, with quantum as the client.
2) exposing an endpoints on quantum, with nova as the client.

My mental model had always been #2, but I'm not wedded to it.

#2 seemed natural to me as the "interface service" (e.g., nova) seemed to
need to be able to "push" an interface binding to quantum (e.g., in the case
where a VM migrates, changing the binding). Also, requring "interface
service" to make a JSON call (ideally with the help of the client lib)
seemed easier than requiring each interface service to expose and
authenticate a new API call. This would be particularly true if an interface
service was already acting as a quantum client for other reasons, which is
the case with nova.

One case where #2 is a bit funky is if quantum is deployed after an
interface service like nova is already running (and thus has already spun up
a set of VMs). It would seem that nova would need to support a method where
it "resends" all of its data. Seems possible, but kind of clunky.

Do you have a strong feeling on this one?

> In that case I'm not entirely sure this should be part of the Quantum API
> at all (for instance if it going to be an Openstack API extension, why
> should it be documented in Quantum API). I can't find a blueprint for
> tracking this piece of work, can you give me a pointer?
>

The original blueprint for this work is:
https://blueprints.launchpad.net/quantum/+spec/quantum-interface-binding-api

There's not a whole lot there. I'm hoping to do this as part of the Quantum
Manager work in nova:
https://blueprints.launchpad.net/nova/+spec/implement-network-api

Just last night I started stubbing out out a possible Controller for this
admin API. I've decided my first cut wasn't how I wanted it to work, so I'm
going to tweak it (hopefully tonight), then I will push the branch and see
what feedback people have. Main goals are:

1) transmit "vif ownership" info to quantum, so we can tell what tenant owns
a vif (and thus whether they are allowed to plug it in to one of their
ports).
2) for plugins that need it, have a channel to report interface bindings.

#2 is somewhat trickier (not clear if it should live in the network-manager,
in the vif-plug driver, somewhere else), but #1 seems pretty
straightforward.

>
> My plan to lock down the API for this week is motivated by the fact that we
> ha...

Read more...

Revision history for this message
Salvatore Orlando (salvatore-orlando) wrote :

Re-proposing for merge.
Community feedback has been addressed.

44. By Salvatore Orlando

Removing unused error response codes

Revision history for this message
Tyler Smith (tylesmit) wrote :

A conflict file accidentally got added:
quantum/tests/unit/test_clientlib.py.THIS

Other than that, LGTM.

review: Needs Fixing
45. By Salvatore Orlando

Removing "excess" file

46. By Salvatore Orlando

Merge trunk
Solving conflict in common/exceptions.py
Removing unused error code from client.py

Revision history for this message
Tyler Smith (tylesmit) wrote :

Great work getting a coherent API spec implemented; looks good.

review: Approve
Revision history for this message
Somik Behera (somikbehera) wrote :
Download full text (3.4 KiB)

Hi Salvatore,

This is a good step in the right direction, towards 1.0!

A reviewed, this changeset, the API wiki, quantum_plugin_base that species the data structure that plugins are mandated to use while passing data to the API layer to transform into XML/JSON for external world to consume.

I see a few shortcomings:

1) quantum_plugin_base.py specifies the "interface" for plugin developers, so that plugin developers can follow this file and implement their plugins without looking at any other place. Along those lines, this "interface" file also specifies the data structure/ "format" that plugins are supposed to use while returning data to the API layer.

Currently, the API layer's expectation regarding data "format" are not aligned with what quantum_plugin_base specifies. Therefore, one or other, or both might need fixing.

2) I noticed we upped the version to /v1.0 and I just wanted ask you if we have appropriate bugs on client-side, if any, already filed to ensure that the client is compliant.

3) One thing I have noticed that is not very consistent, within the API, Unit test and documentation implementation is the data format of for the "attachment"

3.1) This is what plugin_base specifies( API can provide a different format to Northbound API client while keeping the specified format on southbound side plugin side):

    @abstractmethod
    def get_port_details(self, tenant_id, net_id, port_id):
        """
        This method allows the user to retrieve a remote interface
        that is attached to this particular port.

        :returns: a mapping sequence with the following signature:
                    {'port-id': uuid representing the port on
                                 specified quantum network
                     'net-id': uuid representing the particular
                                quantum network
                     'attachment': uuid of the virtual interface
                                   bound to the port, None otherwise
                    }

3.2) Here's the attachment format for Northbound API from wiki:

{
 "attachment":
     {
         "id": "test_interface_identifier"
     }
}

3.3) file quantum/api/views/attachments.py looks for "attachment-id" when looking for the identifier representing the attachment, which is inconsistent on the southbound(quantum_plugin_base) and northbound( API wiki) side.

Looking at file history, it looks like its being changed from attachment -> attachment-id

I would prefer that the southbound side( quantum_plugin_base specification) should be left as attachment if there is not good reason to change the naming as it will break compatibility with all the plugins that have been written, and won't provide any enhanced functionality. We can definitely change things northbound as thats our public API.

3.4) Similarly, net-name was changed to "name" on northbound and southbound side, I have similar feelings that we keep the the southbound side( data format specified in quantum_plugin_base) be kept same while changing northbound. Same is try for port-state -> "state" change.

4) Based on final conclusions, we will need to update unit tests and quantum_plugin_base.py doc as well. I am...

Read more...

review: Needs Fixing (netstack-core)
Revision history for this message
Salvatore Orlando (salvatore-orlando) wrote :
Download full text (6.1 KiB)

Hi Somik,

thanks for your review.

I understand your concerns about different the different data structures in northbound/southbound interfaces. After all, those are exactly the concerns I had when we discussed a uniform data model for the API and the plugin interface a few weeks ago, and I thought we concluded that the API and the plugin interface represented two distinct interfaces.

If we decide to unify these interface, I'd like to use the current API spec as a guideline for changing the plugin interface and not vice-versa, as I would like to keep Quantum API as close as possible the ones exposed by Openstack and other services in the ecosystem. However, this is not a "conditio sine qua non" for me.

You did pretty well in summarizing the differences between northbound and southbound interfaces, I would also add the following:
1) net-id --> id
2) port-id --> id
3) port-state --> state
But I have to agree the attachment-id thing you noticed in your comment #2 is actually a bug, which is due to the fact that the FakePlugin does not implement very well the plugin interface. API code, FakePlugin, and unit tests will be fixed accordingly.

If we want the northbound and southbound interface to have the same shape, then having two interfaces (API and quantum_plugin_base) is not a good idea. We can just decide that the plugin interface is "THE" interface, and the only responsibility of the API layer is converting data structures into the appropriate format; or we can do the opposite. Either way, plugins or clients will be affected.

But, at this stage, I don't think we really need to unify these interfaces. Plugin implementors will talk the language of the plugin interface, while API users will talk the language of the API interface. The API layer will "translate" between these two languages (after all it is not a big effort, as it can be seen from the view builders, which are very small modules). Another advantage of separating interfaces is that the API can evolve indepedently of the plugin interface, and vice-versa; for instance, in the future, object ownership information, fetched from the identity service, could be added to API data structures without interfering at all with the plugin interface.

Please let me know what do you think of my proposal.
I'm totally open to unify API and quantum_plugin_base; my proposal of keeping the interfaces separate arises from the considerations reported above.

Thanks again,
Salvatore

> Hi Salvatore,
>
> This is a good step in the right direction, towards 1.0!
>
> A reviewed, this changeset, the API wiki, quantum_plugin_base that species the
> data structure that plugins are mandated to use while passing data to the API
> layer to transform into XML/JSON for external world to consume.
>
> I see a few shortcomings:
>
> 1) quantum_plugin_base.py specifies the "interface" for plugin developers, so
> that plugin developers can follow this file and implement their plugins
> without looking at any other place. Along those lines, this "interface" file
> also specifies the data structure/ "format" that plugins are supposed to use
> while returning data to the API layer.
>
> Currently, the API layer's...

Read more...

Revision history for this message
Somik Behera (somikbehera) wrote :
Download full text (3.6 KiB)

Hi Salvatore,

Overall, I am in line with your thinking and I think thats a good way to
proceed.

Rest inline.

On Mon, Aug 22, 2011 at 12:43 PM, Salvatore Orlando <
<email address hidden>> wrote:

> Hi Somik,
>
> thanks for your review.
>
> I understand your concerns about different the different data structures in
> northbound/southbound interfaces. After all, those are exactly the concerns
> I had when we discussed a uniform data model for the API and the plugin
> interface a few weeks ago, and I thought we concluded that the API and the
> plugin interface represented two distinct interfaces.
>
> If we decide to unify these interface, I'd like to use the current API spec
> as a guideline for changing the plugin interface and not vice-versa, as I
> would like to keep Quantum API as close as possible the ones exposed by
> Openstack and other services in the ecosystem. However, this is not a
> "conditio sine qua non" for me.
>
> You did pretty well in summarizing the differences between northbound and
> southbound interfaces, I would also add the following:
> 1) net-id --> id
> 2) port-id --> id
> 3) port-state --> state
> But I have to agree the attachment-id thing you noticed in your comment #2
> is actually a bug, which is due to the fact that the FakePlugin does not
> implement very well the plugin interface. API code, FakePlugin, and unit
> tests will be fixed accordingly.

> If we want the northbound and southbound interface to have the same shape,
> then having two interfaces (API and quantum_plugin_base) is not a good idea.
> We can just decide that the plugin interface is "THE" interface, and the
> only responsibility of the API layer is converting data structures into the
> appropriate format; or we can do the opposite. Either way, plugins or
> clients will be affected.
>
>
[SB] I agree that API layer can convert to the quantum_plugin_base -> API
format. Since this change is an API change, we can't really prevent API
clients from getting affected. But, if we don't have pressing need to change
quantum_plugin_base then the atleast plugins wont be affected, which would
be good for this late stage change.

> But, at this stage, I don't think we really need to unify these
> interfaces. Plugin implementors will talk the language of the plugin
> interface, while API users will talk the language of the API interface. The
> API layer will "translate" between these two languages (after all it is not
> a big effort, as it can be seen from the view builders, which are very small
> modules). Another advantage of separating interfaces is that the API can
> evolve indepedently of the plugin interface, and vice-versa; for instance,
> in the future, object ownership information, fetched from the identity
> service, could be added to API data structures without interfering at all
> with the plugin interface.
>
>
[SB] I totally agree with this line of thinking and this would be a great
way to proceed. This approach causes least disruption and provides greatest
flexibility.

> Please let me know what do you think of my proposal.
> I'm totally open to unify API and quantum_plugin_base; my proposal of
> keeping the interfaces separate arise...

Read more...

47. By Salvatore Orlando

Fixing issue in view builders concerning attachment identifiers

Revision history for this message
Salvatore Orlando (salvatore-orlando) wrote :

Hi Somik,

thanks for your reply.
It seems we have now an agreement and can finally close down API v1 for Quantum.
I've updated the branch for taking into account the issue with the 'attachment-id' identifier you spotted.

However, prompted by your review, I double-checked consistency with specification in quantum-plugin-base, and I spotted a problem with get_network_details.
Quantum_plugin_base states that id should return a sequence whose elements have the following attributes: 'net-id', 'net-name', 'net-ifaces', with the latter being a list of interface identifiers.

However:
- the API does not look for 'net-ifaces' at all. It instead invoke get_all_ports and get_port_details for each port; the API therefore works, but not in the most efficient way. This could be fixed quite easily;
- however the plugins (FakePlugin, Cisco, and even Openvswitch), return a sequence
whose elements have the following attributes: 'net-id', 'net-name', 'net-ports' (instead of 'net-ifaces').

I think we probably want to sort this out. We can either change plugin interface documentation, or the plugins. Whatever we decide, we should also update the API to ensure it works in an efficient way.

What's the best approach to tackle this problem in your opinion?

Cheers,
Salvatore

48. By Salvatore Orlando

Merge trunk

Revision history for this message
Somik Behera (somikbehera) wrote :

> Hi Somik,
>
> thanks for your reply.
> It seems we have now an agreement and can finally close down API v1 for
> Quantum.
> I've updated the branch for taking into account the issue with the
> 'attachment-id' identifier you spotted.
>
> However, prompted by your review, I double-checked consistency with
> specification in quantum-plugin-base, and I spotted a problem with
> get_network_details.
> Quantum_plugin_base states that id should return a sequence whose elements
> have the following attributes: 'net-id', 'net-name', 'net-ifaces', with the
> latter being a list of interface identifiers.
>
> However:
> - the API does not look for 'net-ifaces' at all. It instead invoke
> get_all_ports and get_port_details for each port; the API therefore works, but
> not in the most efficient way. This could be fixed quite easily;
> - however the plugins (FakePlugin, Cisco, and even Openvswitch), return a
> sequence
> whose elements have the following attributes: 'net-id', 'net-name', 'net-
> ports' (instead of 'net-ifaces').
>

Good find Salvatore, this is an interesting issue where the plugin interface, API interface and the plugin implementation all diverge.

This is what I suggest, we should keep the API working efficiently, having net-ifaces, doesn't add overhead and makes the API efficient. With that in mind, this seems to be a good thing to keep in quantum_plugin_base interface.

For the other issues, I believe we should file bugs, and once the plugins have fixed themselves, the API can then take advantage of the more performant mechanism.

Iterating over all ports, while ok in short term, will be an issue in large scale environments.

> I think we probably want to sort this out. We can either change plugin
> interface documentation, or the plugins. Whatever we decide, we should also
> update the API to ensure it works in an efficient way.
>
> What's the best approach to tackle this problem in your opinion?
>
> Cheers,
> Salvatore

Everything else looks good!

But, I noticed, the somehow some pep8 violations have creeped into the trunk, I'll be filing those bugs soon.

review: Approve (netstack-core)
Revision history for this message
Salvatore Orlando (salvatore-orlando) wrote :

> > Hi Somik,
> >
> > thanks for your reply.
> > It seems we have now an agreement and can finally close down API v1 for
> > Quantum.
> > I've updated the branch for taking into account the issue with the
> > 'attachment-id' identifier you spotted.
> >
> > However, prompted by your review, I double-checked consistency with
> > specification in quantum-plugin-base, and I spotted a problem with
> > get_network_details.
> > Quantum_plugin_base states that id should return a sequence whose elements
> > have the following attributes: 'net-id', 'net-name', 'net-ifaces', with the
> > latter being a list of interface identifiers.
> >
> > However:
> > - the API does not look for 'net-ifaces' at all. It instead invoke
> > get_all_ports and get_port_details for each port; the API therefore works,
> but
> > not in the most efficient way. This could be fixed quite easily;
> > - however the plugins (FakePlugin, Cisco, and even Openvswitch), return a
> > sequence
> > whose elements have the following attributes: 'net-id', 'net-name', 'net-
> > ports' (instead of 'net-ifaces').
> >
>
> Good find Salvatore, this is an interesting issue where the plugin interface,
> API interface and the plugin implementation all diverge.
>
> This is what I suggest, we should keep the API working efficiently, having
> net-ifaces, doesn't add overhead and makes the API efficient. With that in
> mind, this seems to be a good thing to keep in quantum_plugin_base interface.
>

Definetely agree.

> For the other issues, I believe we should file bugs, and once the plugins have
> fixed themselves, the API can then take advantage of the more performant
> mechanism.
>
> Iterating over all ports, while ok in short term, will be an issue in large
> scale environments.

It is not efficient at all, as it will force clients to do a lot of largely unnecessary round trips.
I will file one or more bugs to improve API and client library accordingly (for instance the client library does not currently leverage the 'detail' actions)

>
> > I think we probably want to sort this out. We can either change plugin
> > interface documentation, or the plugins. Whatever we decide, we should also
> > update the API to ensure it works in an efficient way.
> >
> > What's the best approach to tackle this problem in your opinion?
> >
> > Cheers,
> > Salvatore
>
> Everything else looks good!
>

Thanks!

> But, I noticed, the somehow some pep8 violations have creeped into the trunk,
> I'll be filing those bugs soon.

Good thing you spotted this. I do pep8 tests executing run_tests.sh, which does not check .py files in root directory!

Revision history for this message
dan wendlandt (danwent) wrote :

Hi salvatore, thanks for checking with me about this branch. My main concern before was just regarding the issue of state/status, which we've cleared up. Below are some minor comments, but overall things look great and I am switching my review to Approve.

quantum/api/api_common.py

I have a general question about the API validation performed here, based on a problem I saw a while back. The code uses appears to use syntax like "if not param_value and param['required']:" to determine if a parameter exists. This can be dangerous, as the above expression will evaluate to false if the parameter value is zero, or an empty dictionary, a boolean false, etc. which may in fact be valid values. It seems like really the code should either be checking to see if the key exists in the params dictionary, or use an explicit check for None (e.g., if param_value is None).

quantum/api/faults.py

Is this still left in as a TODO?

+ #MUST RETURN 202???

quantum/db/api.py,

definitely feel free to remove the check_duplicate_net_name method, as your comment suggests.

quantum/tools/batch_config.py

Great that you updated the cli.py and tests. Could you also update batch_config.py? Looks like the name change from 'net-name' at least is an issue, though perhaps there are others.

danwent@ubuntu:~/quantum-api-alignment$ PYTHONPATH=. python ./tools/batch_config.py foo net1=foo:net2=bar
nets: {'net2': ['bar'], 'net1': ['foo']}
Traceback (most recent call last):
  File "./tools/batch_config.py", line 111, in <module>
    create_net_with_attachments(client, net_name, iface_ids)
  File "./tools/batch_config.py", line 46, in create_net_with_attachments
    res = client.create_network(data)
  File "/home/danwent/quantum-api-alignment/quantum/client.py", line 53, in with_params
    ret = self.function(instance, *args)
  File "/home/danwent/quantum-api-alignment/quantum/client.py", line 240, in create_network
    return self.do_request("POST", self.networks_path, body=body)
  File "/home/danwent/quantum-api-alignment/quantum/client.py", line 174, in do_request
    raise EXCEPTIONS[res.status]()
quantum.common.exceptions.BadInputError

review: Approve
Revision history for this message
Salvatore Orlando (salvatore-orlando) wrote :
Download full text (3.5 KiB)

> Hi salvatore, thanks for checking with me about this branch. My main concern
> before was just regarding the issue of state/status, which we've cleared up.
> Below are some minor comments, but overall things look great and I am
> switching my review to Approve.
>
> quantum/api/api_common.py
>
> I have a general question about the API validation performed here, based on a
> problem I saw a while back. The code uses appears to use syntax like "if not
> param_value and param['required']:" to determine if a parameter exists. This
> can be dangerous, as the above expression will evaluate to false if the
> parameter value is zero, or an empty dictionary, a boolean false, etc. which
> may in fact be valid values. It seems like really the code should either be
> checking to see if the key exists in the params dictionary, or use an explicit
> check for None (e.g., if param_value is None).

This is a widely used 'pattern' in python programming. It makes me feel a Pythonista although the C++/C#/Java guy which is in me would probably write:

param_value is None and param['required']

possibly adding a bunch of parenthesis as well :)
On this specific issue, given the way our deserializer currently works, param_value can only be a string. As the deserializer can change in the future, or there might be cases in which an empty string is a valid parameter value, it makes sense to file a bug.

>
> quantum/api/faults.py
>
> Is this still left in as a TODO?
>
> + #MUST RETURN 202???

I think we discussed in an email thread or earlier meeting this issue. From the spec the API should return 202, but the implementation returns 200. It turns out the Openstack API has this problem as well. We 'inherited' it by grabbing their wsgi framework. I was planning to file a bug and have this problem resolved before integration freeze. It is not trivial and I did not want to halt progress on the API work for this issue.

>
> quantum/db/api.py,
>
> definitely feel free to remove the check_duplicate_net_name method, as your
> comment suggests.

I'll file a bug for this; low priority should be enough for this work item.

>
> quantum/tools/batch_config.py
>
> Great that you updated the cli.py and tests. Could you also update
> batch_config.py? Looks like the name change from 'net-name' at least is an
> issue, though perhaps there are others.
>

I've updated them, but the tests are not yet there. I have a rather large merge coming through today with tests and some CLI changes (templated output and removal of the direct plugin mode).

I hope to find some time to fix batch_config as well, but I also need to fix XML deserialization on the client side, so I might run short on time!

>
> danwent@ubuntu:~/quantum-api-alignment$ PYTHONPATH=. python
> ./tools/batch_config.py foo net1=foo:net2=bar
> nets: {'net2': ['bar'], 'net1': ['foo']}
> Traceback (most recent call last):
> File "./tools/batch_config.py", line 111, in <module>
> create_net_with_attachments(client, net_name, iface_ids)
> File "./tools/batch_config.py", line 46, in create_net_with_attachments
> res = client.create_network(data)
> File "/home/danwent/quantum-api-alignment/quantum/...

Read more...

Revision history for this message
dan wendlandt (danwent) wrote :
Download full text (4.9 KiB)

On Thu, Aug 25, 2011 at 2:38 AM, Salvatore Orlando <
<email address hidden>> wrote:

> > Hi salvatore, thanks for checking with me about this branch. My main
> concern
> > before was just regarding the issue of state/status, which we've cleared
> up.
> > Below are some minor comments, but overall things look great and I am
> > switching my review to Approve.
> >
> > quantum/api/api_common.py
> >
> > I have a general question about the API validation performed here, based
> on a
> > problem I saw a while back. The code uses appears to use syntax like "if
> not
> > param_value and param['required']:" to determine if a parameter exists.
> This
> > can be dangerous, as the above expression will evaluate to false if the
> > parameter value is zero, or an empty dictionary, a boolean false, etc.
> which
> > may in fact be valid values. It seems like really the code should either
> be
> > checking to see if the key exists in the params dictionary, or use an
> explicit
> > check for None (e.g., if param_value is None).
>
> This is a widely used 'pattern' in python programming.

Hmm... the python experts I know (I am certainly not one myself) all seem to
recommend against using this approach to check for None, exactly due to the
ambiguity we're talking about. PEP-8 states:

'Also, beware of writing "if x" when you really mean "if x is not None" --
e.g. when testing whether a variable or argument that defaults to None was
set to some other value. The other value might have a type (such as a
container) that could be false in a boolean context!'

It makes me feel a Pythonista although the C++/C#/Java guy which is in me
> would probably write:
>
> param_value is None and param['required']
>
> possibly adding a bunch of parenthesis as well :)
> On this specific issue, given the way our deserializer currently works,
> param_value can only be a string.

Is that true? I was under the impression that a value could also be a
nested array or dictionary, in which case an empty array or dictionary would
be interpreted as an invalid value, even if it was valid. If that is the
case, this could impact anyone writing an extension with nested
arrays/dictionary.

> As the deserializer can change in the future, or there might be cases in
> which an empty string is a valid parameter value, it makes sense to file a
> bug.
>
> >
> > quantum/api/faults.py
> >
> > Is this still left in as a TODO?
> >
> > + #MUST RETURN 202???
>
> I think we discussed in an email thread or earlier meeting this issue. From
> the spec the API should return 202, but the implementation returns 200. It
> turns out the Openstack API has this problem as well. We 'inherited' it by
> grabbing their wsgi framework. I was planning to file a bug and have this
> problem resolved before integration freeze. It is not trivial and I did not
> want to halt progress on the API work for this issue.
>
> >
> > quantum/db/api.py,
> >
> > definitely feel free to remove the check_duplicate_net_name method, as
> your
> > comment suggests.
>
> I'll file a bug for this; low priority should be enough for this work item.
>
> >
> > quantum/tools/batch_config.py
> >
> > Great that you updated the cl...

Read more...

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'etc/quantum.conf'
--- etc/quantum.conf 2011-08-16 23:03:32 +0000
+++ etc/quantum.conf 2011-08-24 11:55:39 +0000
@@ -17,7 +17,7 @@
17[composite:quantum]17[composite:quantum]
18use = egg:Paste#urlmap18use = egg:Paste#urlmap
19/: quantumversions19/: quantumversions
20/v0.1: quantumapi20/v1.0: quantumapi
2121
22[pipeline:quantumapi]22[pipeline:quantumapi]
23pipeline = extensions quantumapiapp23pipeline = extensions quantumapiapp
2424
=== modified file 'quantum/api/__init__.py'
--- quantum/api/__init__.py 2011-08-01 06:22:39 +0000
+++ quantum/api/__init__.py 2011-08-24 11:55:39 +0000
@@ -26,6 +26,7 @@
2626
27from quantum import manager27from quantum import manager
28from quantum.api import faults28from quantum.api import faults
29from quantum.api import attachments
29from quantum.api import networks30from quantum.api import networks
30from quantum.api import ports31from quantum.api import ports
31from quantum.common import flags32from quantum.common import flags
@@ -58,24 +59,29 @@
58 path_prefix=uri_prefix)59 path_prefix=uri_prefix)
59 mapper.resource('port', 'ports',60 mapper.resource('port', 'ports',
60 controller=ports.Controller(plugin),61 controller=ports.Controller(plugin),
62 collection={'detail': 'GET'},
63 member={'detail': 'GET'},
61 parent_resource=dict(member_name='network',64 parent_resource=dict(member_name='network',
62 collection_name=uri_prefix +\65 collection_name=uri_prefix +\
63 'networks'))66 'networks'))
67
68 attachments_ctrl = attachments.Controller(plugin)
69
64 mapper.connect("get_resource",70 mapper.connect("get_resource",
65 uri_prefix + 'networks/{network_id}/' \71 uri_prefix + 'networks/{network_id}/' \
66 'ports/{id}/attachment{.format}',72 'ports/{id}/attachment{.format}',
67 controller=ports.Controller(plugin),73 controller=attachments_ctrl,
68 action="get_resource",74 action="get_resource",
69 conditions=dict(method=['GET']))75 conditions=dict(method=['GET']))
70 mapper.connect("attach_resource",76 mapper.connect("attach_resource",
71 uri_prefix + 'networks/{network_id}/' \77 uri_prefix + 'networks/{network_id}/' \
72 'ports/{id}/attachment{.format}',78 'ports/{id}/attachment{.format}',
73 controller=ports.Controller(plugin),79 controller=attachments_ctrl,
74 action="attach_resource",80 action="attach_resource",
75 conditions=dict(method=['PUT']))81 conditions=dict(method=['PUT']))
76 mapper.connect("detach_resource",82 mapper.connect("detach_resource",
77 uri_prefix + 'networks/{network_id}/' \83 uri_prefix + 'networks/{network_id}/' \
78 'ports/{id}/attachment{.format}',84 'ports/{id}/attachment{.format}',
79 controller=ports.Controller(plugin),85 controller=attachments_ctrl,
80 action="detach_resource",86 action="detach_resource",
81 conditions=dict(method=['DELETE']))87 conditions=dict(method=['DELETE']))
8288
=== modified file 'quantum/api/api_common.py'
--- quantum/api/api_common.py 2011-08-01 14:40:29 +0000
+++ quantum/api/api_common.py 2011-08-24 11:55:39 +0000
@@ -38,7 +38,7 @@
38 for param in params:38 for param in params:
39 param_name = param['param-name']39 param_name = param['param-name']
40 param_value = None40 param_value = None
41 # 1- parse request body41 # Parameters are expected to be in request body only
42 if req.body:42 if req.body:
43 des_body = self._deserialize(req.body,43 des_body = self._deserialize(req.body,
44 req.best_match_content_type())44 req.best_match_content_type())
@@ -50,22 +50,13 @@
50 LOG.error(line)50 LOG.error(line)
51 raise exc.HTTPBadRequest(msg)51 raise exc.HTTPBadRequest(msg)
52 param_value = data.get(param_name, None)52 param_value = data.get(param_name, None)
53 if not param_value:53
54 # 2- parse request headers54 # If the parameter wasn't found and it was required, return 400
55 # prepend param name with a 'x-' prefix55 if not param_value and param['required']:
56 param_value = req.headers.get("x-" + param_name, None)56 msg = ("Failed to parse request. " +
57 # 3- parse request query parameters57 "Parameter: " + param_name + " not specified")
58 if not param_value:58 for line in msg.split('\n'):
59 try:59 LOG.error(line)
60 param_value = req.str_GET[param_name]60 raise exc.HTTPBadRequest(msg)
61 except KeyError:
62 #param not found
63 pass
64 if not param_value and param['required']:
65 msg = ("Failed to parse request. " +
66 "Parameter: " + param_name + " not specified")
67 for line in msg.split('\n'):
68 LOG.error(line)
69 raise exc.HTTPBadRequest(msg)
70 results[param_name] = param_value or param.get('default-value')61 results[param_name] = param_value or param.get('default-value')
71 return results62 return results
7263
=== added file 'quantum/api/attachments.py'
--- quantum/api/attachments.py 1970-01-01 00:00:00 +0000
+++ quantum/api/attachments.py 2011-08-24 11:55:39 +0000
@@ -0,0 +1,86 @@
1# Copyright 2011 Citrix Systems.
2# All Rights Reserved.
3#
4# Licensed under the Apache License, Version 2.0 (the "License"); you may
5# not use this file except in compliance with the License. You may obtain
6# a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13# License for the specific language governing permissions and limitations
14# under the License.
15
16import logging
17
18from webob import exc
19
20from quantum.api import api_common as common
21from quantum.api import faults
22from quantum.api.views import attachments as attachments_view
23from quantum.common import exceptions as exception
24
25LOG = logging.getLogger('quantum.api.ports')
26
27
28class Controller(common.QuantumController):
29 """ Port API controller for Quantum API """
30
31 _attachment_ops_param_list = [{
32 'param-name': 'id',
33 'required': True}, ]
34
35 _serialization_metadata = {
36 "application/xml": {
37 "attributes": {
38 "attachment": ["id"], }
39 },
40 }
41
42 def __init__(self, plugin):
43 self._resource_name = 'attachment'
44 super(Controller, self).__init__(plugin)
45
46 def get_resource(self, request, tenant_id, network_id, id):
47 try:
48 att_data = self._plugin.get_port_details(
49 tenant_id, network_id, id)
50 builder = attachments_view.get_view_builder(request)
51 result = builder.build(att_data)['attachment']
52 return dict(attachment=result)
53 except exception.NetworkNotFound as e:
54 return faults.Fault(faults.NetworkNotFound(e))
55 except exception.PortNotFound as e:
56 return faults.Fault(faults.PortNotFound(e))
57
58 def attach_resource(self, request, tenant_id, network_id, id):
59 try:
60 request_params = \
61 self._parse_request_params(request,
62 self._attachment_ops_param_list)
63 except exc.HTTPError as e:
64 return faults.Fault(e)
65 try:
66 self._plugin.plug_interface(tenant_id, network_id, id,
67 request_params['id'])
68 return exc.HTTPNoContent()
69 except exception.NetworkNotFound as e:
70 return faults.Fault(faults.NetworkNotFound(e))
71 except exception.PortNotFound as e:
72 return faults.Fault(faults.PortNotFound(e))
73 except exception.PortInUse as e:
74 return faults.Fault(faults.PortInUse(e))
75 except exception.AlreadyAttached as e:
76 return faults.Fault(faults.AlreadyAttached(e))
77
78 def detach_resource(self, request, tenant_id, network_id, id):
79 try:
80 self._plugin.unplug_interface(tenant_id,
81 network_id, id)
82 return exc.HTTPNoContent()
83 except exception.NetworkNotFound as e:
84 return faults.Fault(faults.NetworkNotFound(e))
85 except exception.PortNotFound as e:
86 return faults.Fault(faults.PortNotFound(e))
087
=== modified file 'quantum/api/faults.py'
--- quantum/api/faults.py 2011-08-06 06:12:32 +0000
+++ quantum/api/faults.py 2011-08-24 11:55:39 +0000
@@ -31,7 +31,6 @@
31 401: "unauthorized",31 401: "unauthorized",
32 420: "networkNotFound",32 420: "networkNotFound",
33 421: "networkInUse",33 421: "networkInUse",
34 422: "networkNameExists",
35 430: "portNotFound",34 430: "portNotFound",
36 431: "requestedStateInvalid",35 431: "requestedStateInvalid",
37 432: "portInUse",36 432: "portInUse",
@@ -92,22 +91,6 @@
92 explanation = ('Unable to remove the network: attachments still plugged.')91 explanation = ('Unable to remove the network: attachments still plugged.')
9392
9493
95class NetworkNameExists(webob.exc.HTTPClientError):
96 """
97 subclass of :class:`~HTTPClientError`
98
99 This indicates that the server could not set the network name to the
100 specified value because another network for the same tenant already has
101 that name.
102
103 code: 422, title: Network Name Exists
104 """
105 code = 422
106 title = 'Network Name Exists'
107 explanation = ('Unable to set network name: tenant already has network' \
108 ' with same name.')
109
110
111class PortNotFound(webob.exc.HTTPClientError):94class PortNotFound(webob.exc.HTTPClientError):
112 """95 """
113 subclass of :class:`~HTTPClientError`96 subclass of :class:`~HTTPClientError`
11497
=== modified file 'quantum/api/networks.py'
--- quantum/api/networks.py 2011-08-01 17:11:16 +0000
+++ quantum/api/networks.py 2011-08-24 11:55:39 +0000
@@ -29,7 +29,7 @@
29 """ Network API controller for Quantum API """29 """ Network API controller for Quantum API """
3030
31 _network_ops_param_list = [{31 _network_ops_param_list = [{
32 'param-name': 'net-name',32 'param-name': 'name',
33 'required': True}, ]33 'required': True}, ]
3434
35 _serialization_metadata = {35 _serialization_metadata = {
@@ -37,38 +37,43 @@
37 "attributes": {37 "attributes": {
38 "network": ["id", "name"],38 "network": ["id", "name"],
39 "port": ["id", "state"],39 "port": ["id", "state"],
40 },40 "attachment": ["id"]},
41 "plurals": {"networks": "network"}41 "plurals": {"networks": "network",
42 },42 "ports": "port"}},
43 }43 }
4444
45 def __init__(self, plugin):45 def __init__(self, plugin):
46 self._resource_name = 'network'46 self._resource_name = 'network'
47 super(Controller, self).__init__(plugin)47 super(Controller, self).__init__(plugin)
4848
49 def index(self, request, tenant_id):
50 """ Returns a list of network ids """
51 #TODO: this should be for a given tenant!!!
52 return self._items(request, tenant_id)
53
54 def _item(self, req, tenant_id, network_id,49 def _item(self, req, tenant_id, network_id,
55 net_details=True, port_details=False):50 net_details=True, port_details=False):
56 # We expect get_network_details to return information51 # We expect get_network_details to return information
57 # concerning logical ports as well.52 # concerning logical ports as well.
58 network = self._plugin.get_network_details(53 network = self._plugin.get_network_details(
59 tenant_id, network_id)54 tenant_id, network_id)
55 port_list = self._plugin.get_all_ports(
56 tenant_id, network_id)
57 ports_data = [self._plugin.get_port_details(
58 tenant_id, network_id, port['port-id'])
59 for port in port_list]
60 builder = networks_view.get_view_builder(req)60 builder = networks_view.get_view_builder(req)
61 result = builder.build(network, net_details, port_details)['network']61 result = builder.build(network, net_details,
62 ports_data, port_details)['network']
62 return dict(network=result)63 return dict(network=result)
6364
64 def _items(self, req, tenant_id, net_details=False, port_details=False):65 def _items(self, req, tenant_id, net_details=False):
65 """ Returns a list of networks. """66 """ Returns a list of networks. """
66 networks = self._plugin.get_all_networks(tenant_id)67 networks = self._plugin.get_all_networks(tenant_id)
67 builder = networks_view.get_view_builder(req)68 builder = networks_view.get_view_builder(req)
68 result = [builder.build(network, net_details, port_details)['network']69 result = [builder.build(network, net_details)['network']
69 for network in networks]70 for network in networks]
70 return dict(networks=result)71 return dict(networks=result)
7172
73 def index(self, request, tenant_id):
74 """ Returns a list of network ids """
75 return self._items(request, tenant_id)
76
72 def show(self, request, tenant_id, id):77 def show(self, request, tenant_id, id):
73 """ Returns network details for the given network id """78 """ Returns network details for the given network id """
74 try:79 try:
@@ -80,23 +85,13 @@
80 def detail(self, request, **kwargs):85 def detail(self, request, **kwargs):
81 tenant_id = kwargs.get('tenant_id')86 tenant_id = kwargs.get('tenant_id')
82 network_id = kwargs.get('id')87 network_id = kwargs.get('id')
83 try:88 if network_id:
84 if network_id:89 # show details for a given network
85 # show details for a given network90 return self._item(request, tenant_id, network_id,
86 return self._item(request, tenant_id, network_id,91 net_details=True, port_details=True)
87 net_details=True, port_details=True)92 else:
88 else:93 # show details for all networks
89 # show details for all networks94 return self._items(request, tenant_id, net_details=True)
90 return self._items(request, tenant_id,
91 net_details=True, port_details=False)
92 network = self._plugin.get_network_details(
93 tenant_id, id)
94 builder = networks_view.get_view_builder(request)
95 #build response with details
96 result = builder.build(network, True)
97 return dict(networks=result)
98 except exception.NetworkNotFound as e:
99 return faults.Fault(faults.NetworkNotFound(e))
10095
101 def create(self, request, tenant_id):96 def create(self, request, tenant_id):
102 """ Creates a new network for a given tenant """97 """ Creates a new network for a given tenant """
@@ -107,15 +102,13 @@
107 self._network_ops_param_list)102 self._network_ops_param_list)
108 except exc.HTTPError as e:103 except exc.HTTPError as e:
109 return faults.Fault(e)104 return faults.Fault(e)
110 try:105 network = self._plugin.\
111 network = self._plugin.\106 create_network(tenant_id,
112 create_network(tenant_id,107 request_params['name'])
113 request_params['net-name'])108 builder = networks_view.get_view_builder(request)
114 builder = networks_view.get_view_builder(request)109 result = builder.build(network)['network']
115 result = builder.build(network)110 #MUST RETURN 202???
116 return dict(networks=result)111 return dict(network=result)
117 except exception.NetworkNameExists as e:
118 return faults.Fault(faults.NetworkNameExists(e))
119112
120 def update(self, request, tenant_id, id):113 def update(self, request, tenant_id, id):
121 """ Updates the name for the network with the given id """114 """ Updates the name for the network with the given id """
@@ -127,18 +120,16 @@
127 return faults.Fault(e)120 return faults.Fault(e)
128 try:121 try:
129 self._plugin.rename_network(tenant_id, id,122 self._plugin.rename_network(tenant_id, id,
130 request_params['net-name'])123 request_params['name'])
131 return exc.HTTPAccepted()124 return exc.HTTPNoContent()
132 except exception.NetworkNotFound as e:125 except exception.NetworkNotFound as e:
133 return faults.Fault(faults.NetworkNotFound(e))126 return faults.Fault(faults.NetworkNotFound(e))
134 except exception.NetworkNameExists as e:
135 return faults.Fault(faults.NetworkNameExists(e))
136127
137 def delete(self, request, tenant_id, id):128 def delete(self, request, tenant_id, id):
138 """ Destroys the network with the given id """129 """ Destroys the network with the given id """
139 try:130 try:
140 self._plugin.delete_network(tenant_id, id)131 self._plugin.delete_network(tenant_id, id)
141 return exc.HTTPAccepted()132 return exc.HTTPNoContent()
142 except exception.NetworkNotFound as e:133 except exception.NetworkNotFound as e:
143 return faults.Fault(faults.NetworkNotFound(e))134 return faults.Fault(faults.NetworkNotFound(e))
144 except exception.NetworkInUse as e:135 except exception.NetworkInUse as e:
145136
=== modified file 'quantum/api/ports.py'
--- quantum/api/ports.py 2011-07-22 09:51:22 +0000
+++ quantum/api/ports.py 2011-08-24 11:55:39 +0000
@@ -29,55 +29,79 @@
29 """ Port API controller for Quantum API """29 """ Port API controller for Quantum API """
3030
31 _port_ops_param_list = [{31 _port_ops_param_list = [{
32 'param-name': 'port-state',32 'param-name': 'state',
33 'default-value': 'DOWN',33 'default-value': 'DOWN',
34 'required': False}, ]34 'required': False}, ]
3535
36 _attachment_ops_param_list = [{
37 'param-name': 'attachment-id',
38 'required': True}, ]
39
40 _serialization_metadata = {36 _serialization_metadata = {
41 "application/xml": {37 "application/xml": {
42 "attributes": {38 "attributes": {
43 "port": ["id", "state"], },39 "port": ["id", "state"],
44 "plurals": {"ports": "port"}40 "attachment": ["id"]},
45 },41 "plurals": {"ports": "port"}},
46 }42 }
4743
48 def __init__(self, plugin):44 def __init__(self, plugin):
49 self._resource_name = 'port'45 self._resource_name = 'port'
50 super(Controller, self).__init__(plugin)46 super(Controller, self).__init__(plugin)
5147
48 def _items(self, request, tenant_id, network_id,
49 port_details=False):
50 """ Returns a list of ports. """
51 try:
52 port_list = self._plugin.get_all_ports(tenant_id, network_id)
53 builder = ports_view.get_view_builder(request)
54
55 # Load extra data for ports if required.
56 if port_details:
57 port_list_detail = \
58 [self._plugin.get_port_details(
59 tenant_id, network_id, port['port-id'])
60 for port in port_list]
61 port_list = port_list_detail
62
63 result = [builder.build(port, port_details)['port']
64 for port in port_list]
65 return dict(ports=result)
66 except exception.NetworkNotFound as e:
67 return faults.Fault(faults.NetworkNotFound(e))
68
69 def _item(self, request, tenant_id, network_id, port_id,
70 att_details=False):
71 """ Returns a specific port. """
72 port = self._plugin.get_port_details(
73 tenant_id, network_id, port_id)
74 builder = ports_view.get_view_builder(request)
75 result = builder.build(port, port_details=True,
76 att_details=att_details)['port']
77 return dict(port=result)
78
52 def index(self, request, tenant_id, network_id):79 def index(self, request, tenant_id, network_id):
53 """ Returns a list of port ids for a given network """80 """ Returns a list of port ids for a given network """
54 return self._items(request, tenant_id, network_id, is_detail=False)81 return self._items(request, tenant_id, network_id, port_details=False)
55
56 def _items(self, request, tenant_id, network_id, is_detail):
57 """ Returns a list of networks. """
58 try:
59 ports = self._plugin.get_all_ports(tenant_id, network_id)
60 builder = ports_view.get_view_builder(request)
61 result = [builder.build(port, is_detail)['port']
62 for port in ports]
63 return dict(ports=result)
64 except exception.NetworkNotFound as e:
65 return faults.Fault(faults.NetworkNotFound(e))
6682
67 def show(self, request, tenant_id, network_id, id):83 def show(self, request, tenant_id, network_id, id):
68 """ Returns port details for given port and network """84 """ Returns port details for given port and network """
69 try:85 try:
70 port = self._plugin.get_port_details(86 return self._item(request, tenant_id, network_id, id)
71 tenant_id, network_id, id)
72 builder = ports_view.get_view_builder(request)
73 #build response with details
74 result = builder.build(port, True)['port']
75 return dict(port=result)
76 except exception.NetworkNotFound as e:87 except exception.NetworkNotFound as e:
77 return faults.Fault(faults.NetworkNotFound(e))88 return faults.Fault(faults.NetworkNotFound(e))
78 except exception.PortNotFound as e:89 except exception.PortNotFound as e:
79 return faults.Fault(faults.PortNotFound(e))90 return faults.Fault(faults.PortNotFound(e))
8091
92 def detail(self, request, **kwargs):
93 tenant_id = kwargs.get('tenant_id')
94 network_id = kwargs.get('network_id')
95 port_id = kwargs.get('id')
96 if port_id:
97 # show details for a given network
98 return self._item(request, tenant_id,
99 network_id, port_id, att_details=True)
100 else:
101 # show details for all port
102 return self._items(request, tenant_id,
103 network_id, port_details=True)
104
81 def create(self, request, tenant_id, network_id):105 def create(self, request, tenant_id, network_id):
82 """ Creates a new port for a given network """106 """ Creates a new port for a given network """
83 #look for port state in request107 #look for port state in request
@@ -89,10 +113,10 @@
89 try:113 try:
90 port = self._plugin.create_port(tenant_id,114 port = self._plugin.create_port(tenant_id,
91 network_id,115 network_id,
92 request_params['port-state'])116 request_params['state'])
93 builder = ports_view.get_view_builder(request)117 builder = ports_view.get_view_builder(request)
94 result = builder.build(port)118 result = builder.build(port)['port']
95 return dict(ports=result)119 return dict(port=result)
96 except exception.NetworkNotFound as e:120 except exception.NetworkNotFound as e:
97 return faults.Fault(faults.NetworkNotFound(e))121 return faults.Fault(faults.NetworkNotFound(e))
98 except exception.StateInvalid as e:122 except exception.StateInvalid as e:
@@ -107,11 +131,9 @@
107 except exc.HTTPError as e:131 except exc.HTTPError as e:
108 return faults.Fault(e)132 return faults.Fault(e)
109 try:133 try:
110 port = self._plugin.update_port(tenant_id, network_id, id,134 self._plugin.update_port(tenant_id, network_id, id,
111 request_params['port-state'])135 request_params['state'])
112 builder = ports_view.get_view_builder(request)136 return exc.HTTPNoContent()
113 result = builder.build(port, True)
114 return dict(ports=result)
115 except exception.NetworkNotFound as e:137 except exception.NetworkNotFound as e:
116 return faults.Fault(faults.NetworkNotFound(e))138 return faults.Fault(faults.NetworkNotFound(e))
117 except exception.PortNotFound as e:139 except exception.PortNotFound as e:
@@ -124,53 +146,10 @@
124 #look for port state in request146 #look for port state in request
125 try:147 try:
126 self._plugin.delete_port(tenant_id, network_id, id)148 self._plugin.delete_port(tenant_id, network_id, id)
127 return exc.HTTPAccepted()149 return exc.HTTPNoContent()
128 # TODO(salvatore-orlando): Handle portInUse error150 except exception.NetworkNotFound as e:
129 except exception.NetworkNotFound as e:151 return faults.Fault(faults.NetworkNotFound(e))
130 return faults.Fault(faults.NetworkNotFound(e))152 except exception.PortNotFound as e:
131 except exception.PortNotFound as e:153 return faults.Fault(faults.PortNotFound(e))
132 return faults.Fault(faults.PortNotFound(e))154 except exception.PortInUse as e:
133 except exception.PortInUse as e:155 return faults.Fault(faults.PortInUse(e))
134 return faults.Fault(faults.PortInUse(e))
135
136 def get_resource(self, request, tenant_id, network_id, id):
137 try:
138 result = self._plugin.get_port_details(
139 tenant_id, network_id, id).get('attachment-id',
140 None)
141 return dict(attachment=result)
142 except exception.NetworkNotFound as e:
143 return faults.Fault(faults.NetworkNotFound(e))
144 except exception.PortNotFound as e:
145 return faults.Fault(faults.PortNotFound(e))
146
147 def attach_resource(self, request, tenant_id, network_id, id):
148 try:
149 request_params = \
150 self._parse_request_params(request,
151 self._attachment_ops_param_list)
152 except exc.HTTPError as e:
153 return faults.Fault(e)
154 try:
155 self._plugin.plug_interface(tenant_id,
156 network_id, id,
157 request_params['attachment-id'])
158 return exc.HTTPAccepted()
159 except exception.NetworkNotFound as e:
160 return faults.Fault(faults.NetworkNotFound(e))
161 except exception.PortNotFound as e:
162 return faults.Fault(faults.PortNotFound(e))
163 except exception.PortInUse as e:
164 return faults.Fault(faults.PortInUse(e))
165 except exception.AlreadyAttached as e:
166 return faults.Fault(faults.AlreadyAttached(e))
167
168 def detach_resource(self, request, tenant_id, network_id, id):
169 try:
170 self._plugin.unplug_interface(tenant_id,
171 network_id, id)
172 return exc.HTTPAccepted()
173 except exception.NetworkNotFound as e:
174 return faults.Fault(faults.NetworkNotFound(e))
175 except exception.PortNotFound as e:
176 return faults.Fault(faults.PortNotFound(e))
177156
=== modified file 'quantum/api/versions.py'
--- quantum/api/versions.py 2011-05-31 17:15:00 +0000
+++ quantum/api/versions.py 2011-08-24 11:55:39 +0000
@@ -31,11 +31,11 @@
31 """Respond to a request for all Quantum API versions."""31 """Respond to a request for all Quantum API versions."""
32 version_objs = [32 version_objs = [
33 {33 {
34 "id": "v0.1",34 "id": "v1.0",
35 "status": "CURRENT",35 "status": "CURRENT",
36 },36 },
37 {37 {
38 "id": "v1.0",38 "id": "v1.1",
39 "status": "FUTURE",39 "status": "FUTURE",
40 },40 },
41 ]41 ]
4242
=== added file 'quantum/api/views/attachments.py'
--- quantum/api/views/attachments.py 1970-01-01 00:00:00 +0000
+++ quantum/api/views/attachments.py 2011-08-24 11:55:39 +0000
@@ -0,0 +1,37 @@
1# vim: tabstop=4 shiftwidth=4 softtabstop=4
2
3# Copyright 2011 Citrix Systems
4# All Rights Reserved.
5#
6# Licensed under the Apache License, Version 2.0 (the "License"); you may
7# not use this file except in compliance with the License. You may obtain
8# a copy of the License at
9#
10# http://www.apache.org/licenses/LICENSE-2.0
11#
12# Unless required by applicable law or agreed to in writing, software
13# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
14# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
15# License for the specific language governing permissions and limitations
16# under the License.
17
18
19def get_view_builder(req):
20 base_url = req.application_url
21 return ViewBuilder(base_url)
22
23
24class ViewBuilder(object):
25
26 def __init__(self, base_url):
27 """
28 :param base_url: url of the root wsgi application
29 """
30 self.base_url = base_url
31
32 def build(self, attachment_data):
33 """Generic method used to generate an attachment entity."""
34 if attachment_data['attachment']:
35 return dict(attachment=dict(id=attachment_data['attachment']))
36 else:
37 return dict(attachment={})
038
=== modified file 'quantum/api/views/networks.py'
--- quantum/api/views/networks.py 2011-08-01 01:21:59 +0000
+++ quantum/api/views/networks.py 2011-08-24 11:55:39 +0000
@@ -15,8 +15,6 @@
15# License for the specific language governing permissions and limitations15# License for the specific language governing permissions and limitations
16# under the License.16# under the License.
1717
18from quantum.api.views import ports as ports_view
19
2018
21def get_view_builder(req):19def get_view_builder(req):
22 base_url = req.application_url20 base_url = req.application_url
@@ -31,17 +29,16 @@
31 """29 """
32 self.base_url = base_url30 self.base_url = base_url
3331
34 def build(self, network_data, net_detail=False, port_detail=False):32 def build(self, network_data, net_detail=False,
33 ports_data=None, port_detail=False):
35 """Generic method used to generate a network entity."""34 """Generic method used to generate a network entity."""
36 if net_detail:35 if net_detail:
37 network = self._build_detail(network_data)36 network = self._build_detail(network_data)
38 else:37 else:
39 network = self._build_simple(network_data)38 network = self._build_simple(network_data)
40 if port_detail:39 if port_detail:
41 builder = ports_view.ViewBuilder(self.base_url)40 ports = [self._build_port(port_data) for port_data in ports_data]
42 ports = [builder.build(port_data, port_detail)['port']41 network['network']['ports'] = ports
43 for port_data in network_data['net-ports'].values()]
44 network['ports'] = ports
45 return network42 return network
4643
47 def _build_simple(self, network_data):44 def _build_simple(self, network_data):
@@ -55,6 +52,8 @@
5552
56 def _build_port(self, port_data):53 def _build_port(self, port_data):
57 """Return details about a specific logical port."""54 """Return details about a specific logical port."""
58 return dict(port=dict(id=port_data['port-id'],55 port_dict = dict(id=port_data['port-id'],
59 state=port_data['port-state'],56 state=port_data['port-state'])
60 attachment=port_data['attachment']))57 if port_data['attachment']:
58 port_dict['attachment'] = dict(id=port_data['attachment'])
59 return port_dict
6160
=== modified file 'quantum/api/views/ports.py'
--- quantum/api/views/ports.py 2011-07-22 09:51:22 +0000
+++ quantum/api/views/ports.py 2011-08-24 11:55:39 +0000
@@ -29,19 +29,11 @@
29 """29 """
30 self.base_url = base_url30 self.base_url = base_url
3131
32 def build(self, port_data, is_detail=False):32 def build(self, port_data, port_details=False, att_details=False):
33 """Generic method used to generate a port entity."""33 """Generic method used to generate a port entity."""
34 if is_detail:34 port = dict(port=dict(id=port_data['port-id']))
35 port = self._build_detail(port_data)35 if port_details:
36 else:36 port['port']['state'] = port_data['port-state']
37 port = self._build_simple(port_data)37 if att_details and port_data['attachment']:
38 port['port']['attachment'] = dict(id=port_data['attachment'])
38 return port39 return port
39
40 def _build_simple(self, port_data):
41 """Return a simple model of a port."""
42 return dict(port=dict(id=port_data['port-id']))
43
44 def _build_detail(self, port_data):
45 """Return a simple model of a port (with its state)."""
46 return dict(port=dict(id=port_data['port-id'],
47 state=port_data['port-state']))
4840
=== modified file 'quantum/cli.py'
--- quantum/cli.py 2011-08-23 19:36:06 +0000
+++ quantum/cli.py 2011-08-24 11:55:39 +0000
@@ -69,7 +69,7 @@
69 LOG.debug(res)69 LOG.debug(res)
70 nid = None70 nid = None
71 try:71 try:
72 nid = res["networks"]["network"]["id"]72 nid = res["network"]["id"]
73 except Exception, e:73 except Exception, e:
74 print "Failed to create network"74 print "Failed to create network"
75 # TODO(bgh): grab error details from ws request result75 # TODO(bgh): grab error details from ws request result
@@ -104,7 +104,7 @@
104def api_detail_net(client, *args):104def api_detail_net(client, *args):
105 tid, nid = args105 tid, nid = args
106 try:106 try:
107 res = client.show_network_details(nid)["networks"]["network"]107 res = client.show_network_details(nid)["network"]
108 except Exception, e:108 except Exception, e:
109 LOG.error("Failed to get network details: %s" % e)109 LOG.error("Failed to get network details: %s" % e)
110 return110 return
@@ -121,7 +121,7 @@
121 pid = port["id"]121 pid = port["id"]
122 res = client.show_port_attachment(nid, pid)122 res = client.show_port_attachment(nid, pid)
123 LOG.debug(res)123 LOG.debug(res)
124 remote_iface = res["attachment"]124 remote_iface = res["attachment"]["id"]
125 print "\tRemote interface:%s" % remote_iface125 print "\tRemote interface:%s" % remote_iface
126126
127127
@@ -133,7 +133,7 @@
133133
134def api_rename_net(client, *args):134def api_rename_net(client, *args):
135 tid, nid, name = args135 tid, nid, name = args
136 data = {'network': {'net-name': '%s' % name}}136 data = {'network': {'name': '%s' % name}}
137 try:137 try:
138 res = client.update_network(nid, data)138 res = client.update_network(nid, data)
139 except Exception, e:139 except Exception, e:
@@ -179,7 +179,7 @@
179 except Exception, e:179 except Exception, e:
180 LOG.error("Failed to create port: %s" % e)180 LOG.error("Failed to create port: %s" % e)
181 return181 return
182 new_port = res["ports"]["port"]["id"]182 new_port = res["port"]["id"]
183 print "Created Virtual Port:%s " \183 print "Created Virtual Port:%s " \
184 "on Virtual Network:%s" % (new_port, nid)184 "on Virtual Network:%s" % (new_port, nid)
185185
@@ -214,16 +214,17 @@
214def api_detail_port(client, *args):214def api_detail_port(client, *args):
215 tid, nid, pid = args215 tid, nid, pid = args
216 try:216 try:
217 port = client.show_port_details(nid, pid)["ports"]["port"]217 port = client.show_port_details(nid, pid)["port"]
218 att = client.show_port_attachment(nid, pid)
218 except Exception, e:219 except Exception, e:
219 LOG.error("Failed to get port details: %s" % e)220 LOG.error("Failed to get port details: %s" % e)
220 return221 return
221222
222 id = port["id"]223 id = port['id']
223 attachment = port["attachment"]224 interface_id = att['id']
224 LOG.debug(port)225 LOG.debug(port)
225 print "Virtual Port:%s on Virtual Network:%s " \226 print "Virtual Port:%s on Virtual Network:%s " \
226 "contains remote interface:%s" % (pid, nid, attachment)227 "contains remote interface:%s" % (pid, nid, interface_id)
227228
228229
229def plug_iface(manager, *args):230def plug_iface(manager, *args):
@@ -236,7 +237,7 @@
236def api_plug_iface(client, *args):237def api_plug_iface(client, *args):
237 tid, nid, pid, vid = args238 tid, nid, pid, vid = args
238 try:239 try:
239 data = {'port': {'attachment-id': '%s' % vid}}240 data = {'attachment': {'id': '%s' % vid}}
240 res = client.attach_resource(nid, pid, data)241 res = client.attach_resource(nid, pid, data)
241 except Exception, e:242 except Exception, e:
242 LOG.error("Failed to plug iface \"%s\" to port \"%s\": %s" % (vid,243 LOG.error("Failed to plug iface \"%s\" to port \"%s\": %s" % (vid,
243244
=== modified file 'quantum/client.py'
--- quantum/client.py 2011-08-24 10:15:54 +0000
+++ quantum/client.py 2011-08-24 11:55:39 +0000
@@ -27,13 +27,10 @@
27 401: exceptions.NotAuthorized,27 401: exceptions.NotAuthorized,
28 420: exceptions.NetworkNotFound,28 420: exceptions.NetworkNotFound,
29 421: exceptions.NetworkInUse,29 421: exceptions.NetworkInUse,
30 422: exceptions.NetworkNameExists,
31 430: exceptions.PortNotFound,30 430: exceptions.PortNotFound,
32 431: exceptions.StateInvalid,31 431: exceptions.StateInvalid,
33 432: exceptions.PortInUse,32 432: exceptions.PortInUse,
34 440: exceptions.AlreadyAttached,33 440: exceptions.AlreadyAttached}
35 441: exceptions.AttachmentNotReady,
36}
3734
3835
39class ApiCall(object):36class ApiCall(object):
@@ -72,7 +69,7 @@
7269
73 def __init__(self, host="127.0.0.1", port=9696, use_ssl=False, tenant=None,70 def __init__(self, host="127.0.0.1", port=9696, use_ssl=False, tenant=None,
74 format="xml", testingStub=None, key_file=None, cert_file=None,71 format="xml", testingStub=None, key_file=None, cert_file=None,
75 logger=None, action_prefix="/v0.1/tenants/{tenant_id}"):72 logger=None, action_prefix="/v1.0/tenants/{tenant_id}"):
76 """73 """
77 Creates a new client to some service.74 Creates a new client to some service.
7875
7976
=== modified file 'quantum/common/exceptions.py'
--- quantum/common/exceptions.py 2011-08-18 19:49:20 +0000
+++ quantum/common/exceptions.py 2011-08-24 11:55:39 +0000
@@ -111,16 +111,6 @@
111 "already plugged into port %(att_port_id)s")111 "already plugged into port %(att_port_id)s")
112112
113113
114class AttachmentNotReady(QuantumException):
115 message = _("The attachment %(att_id)s is not ready")
116
117
118class NetworkNameExists(QuantumException):
119 message = _("Unable to set network name to %(net_name). " \
120 "Network with id %(net_id) already has this name for " \
121 "tenant %(tenant_id)")
122
123
124class Duplicate(Error):114class Duplicate(Error):
125 pass115 pass
126116
127117
=== modified file 'quantum/common/wsgi.py'
--- quantum/common/wsgi.py 2011-08-02 04:49:32 +0000
+++ quantum/common/wsgi.py 2011-08-24 11:55:39 +0000
@@ -122,6 +122,7 @@
122 Based on the query extension then the Accept header.122 Based on the query extension then the Accept header.
123123
124 """124 """
125 # First lookup http request
125 parts = self.path.rsplit('.', 1)126 parts = self.path.rsplit('.', 1)
126 LOG.debug("Request parts:%s", parts)127 LOG.debug("Request parts:%s", parts)
127 if len(parts) > 1:128 if len(parts) > 1:
@@ -129,21 +130,26 @@
129 if format in ['json', 'xml']:130 if format in ['json', 'xml']:
130 return 'application/{0}'.format(parts[1])131 return 'application/{0}'.format(parts[1])
131132
133 #Then look up content header
134 type_from_header = self.get_content_type()
135 if type_from_header:
136 return type_from_header
132 ctypes = ['application/json', 'application/xml']137 ctypes = ['application/json', 'application/xml']
138
139 #Finally search in Accept-* headers
133 bm = self.accept.best_match(ctypes)140 bm = self.accept.best_match(ctypes)
134 return bm or 'application/json'141 return bm or 'application/json'
135142
136 def get_content_type(self):143 def get_content_type(self):
137 allowed_types = ("application/xml", "application/json")144 allowed_types = ("application/xml", "application/json")
138 if not "Content-Type" in self.headers:145 if not "Content-Type" in self.headers:
139 msg = _("Missing Content-Type")146 LOG.debug(_("Missing Content-Type"))
140 LOG.debug(msg)147 return None
141 raise webob.exc.HTTPBadRequest(msg)
142 type = self.content_type148 type = self.content_type
143 if type in allowed_types:149 if type in allowed_types:
144 return type150 return type
145 LOG.debug(_("Wrong Content-Type: %s") % type)151 LOG.debug(_("Wrong Content-Type: %s") % type)
146 raise webob.exc.HTTPBadRequest("Invalid content type")152 return None
147153
148154
149class Application(object):155class Application(object):
150156
=== modified file 'quantum/db/api.py'
--- quantum/db/api.py 2011-08-10 04:58:15 +0000
+++ quantum/db/api.py 2011-08-24 11:55:39 +0000
@@ -17,6 +17,8 @@
17# @author: Brad Hall, Nicira Networks, Inc.17# @author: Brad Hall, Nicira Networks, Inc.
18# @author: Dan Wendlandt, Nicira Networks, Inc.18# @author: Dan Wendlandt, Nicira Networks, Inc.
1919
20import logging
21
20from sqlalchemy import create_engine22from sqlalchemy import create_engine
21from sqlalchemy.orm import sessionmaker, exc23from sqlalchemy.orm import sessionmaker, exc
2224
@@ -27,6 +29,7 @@
27_ENGINE = None29_ENGINE = None
28_MAKER = None30_MAKER = None
29BASE = models.BASE31BASE = models.BASE
32LOG = logging.getLogger('quantum.db.api')
3033
3134
32def configure_db(options):35def configure_db(options):
@@ -78,6 +81,11 @@
7881
7982
80def _check_duplicate_net_name(tenant_id, net_name):83def _check_duplicate_net_name(tenant_id, net_name):
84 """Checks whether a network with the same name
85 already exists for the tenant.
86 """
87
88 #TODO(salvatore-orlando): Not used anymore - candidate for removal
81 session = get_session()89 session = get_session()
82 try:90 try:
83 net = session.query(models.Network).\91 net = session.query(models.Network).\
@@ -94,7 +102,6 @@
94def network_create(tenant_id, name):102def network_create(tenant_id, name):
95 session = get_session()103 session = get_session()
96104
97 _check_duplicate_net_name(tenant_id, name)
98 with session.begin():105 with session.begin():
99 net = models.Network(tenant_id, name)106 net = models.Network(tenant_id, name)
100 session.add(net)107 session.add(net)
101108
=== modified file 'quantum/db/models.py'
--- quantum/db/models.py 2011-08-02 04:49:32 +0000
+++ quantum/db/models.py 2011-08-24 11:55:39 +0000
@@ -70,13 +70,14 @@
70 uuid = Column(String(255), primary_key=True)70 uuid = Column(String(255), primary_key=True)
71 network_id = Column(String(255), ForeignKey("networks.uuid"),71 network_id = Column(String(255), ForeignKey("networks.uuid"),
72 nullable=False)72 nullable=False)
73 interface_id = Column(String(255))73 interface_id = Column(String(255), nullable=True)
74 # Port state - Hardcoding string value at the moment74 # Port state - Hardcoding string value at the moment
75 state = Column(String(8))75 state = Column(String(8))
7676
77 def __init__(self, network_id):77 def __init__(self, network_id):
78 self.uuid = str(uuid.uuid4())78 self.uuid = str(uuid.uuid4())
79 self.network_id = network_id79 self.network_id = network_id
80 self.interface_id = None
80 self.state = "DOWN"81 self.state = "DOWN"
8182
82 def __repr__(self):83 def __repr__(self):
8384
=== modified file 'quantum/plugins/SamplePlugin.py'
--- quantum/plugins/SamplePlugin.py 2011-08-10 04:58:15 +0000
+++ quantum/plugins/SamplePlugin.py 2011-08-24 11:55:39 +0000
@@ -352,7 +352,7 @@
352 LOG.debug("FakePlugin.get_port_details() called")352 LOG.debug("FakePlugin.get_port_details() called")
353 port = self._get_port(tenant_id, net_id, port_id)353 port = self._get_port(tenant_id, net_id, port_id)
354 return {'port-id': str(port.uuid),354 return {'port-id': str(port.uuid),
355 'attachment-id': port.interface_id,355 'attachment': port.interface_id,
356 'port-state': port.state}356 'port-state': port.state}
357357
358 def create_port(self, tenant_id, net_id, port_state=None):358 def create_port(self, tenant_id, net_id, port_state=None):
@@ -407,10 +407,10 @@
407 specified Virtual Network.407 specified Virtual Network.
408 """408 """
409 LOG.debug("FakePlugin.plug_interface() called")409 LOG.debug("FakePlugin.plug_interface() called")
410 port = self._get_port(tenant_id, net_id, port_id)
410 # Validate attachment411 # Validate attachment
411 self._validate_attachment(tenant_id, net_id, port_id,412 self._validate_attachment(tenant_id, net_id, port_id,
412 remote_interface_id)413 remote_interface_id)
413 port = self._get_port(tenant_id, net_id, port_id)
414 if port['interface_id']:414 if port['interface_id']:
415 raise exc.PortInUse(net_id=net_id, port_id=port_id,415 raise exc.PortInUse(net_id=net_id, port_id=port_id,
416 att_id=port['interface_id'])416 att_id=port['interface_id'])
417417
=== modified file 'tests/unit/test_api.py'
--- tests/unit/test_api.py 2011-08-23 19:36:06 +0000
+++ tests/unit/test_api.py 2011-08-24 11:55:39 +0000
@@ -48,7 +48,7 @@
48 if expected_res_status == 200:48 if expected_res_status == 200:
49 network_data = Serializer().deserialize(network_res.body,49 network_data = Serializer().deserialize(network_res.body,
50 content_type)50 content_type)
51 return network_data['networks']['network']['id']51 return network_data['network']['id']
5252
53 def _create_port(self, network_id, port_state, format,53 def _create_port(self, network_id, port_state, format,
54 custom_req_body=None, expected_res_status=200):54 custom_req_body=None, expected_res_status=200):
@@ -61,7 +61,7 @@
61 self.assertEqual(port_res.status_int, expected_res_status)61 self.assertEqual(port_res.status_int, expected_res_status)
62 if expected_res_status == 200:62 if expected_res_status == 200:
63 port_data = Serializer().deserialize(port_res.body, content_type)63 port_data = Serializer().deserialize(port_res.body, content_type)
64 return port_data['ports']['port']['id']64 return port_data['port']['id']
6565
66 def _test_create_network(self, format):66 def _test_create_network(self, format):
67 LOG.debug("_test_create_network - format:%s - START", format)67 LOG.debug("_test_create_network - format:%s - START", format)
@@ -74,8 +74,7 @@
74 self.assertEqual(show_network_res.status_int, 200)74 self.assertEqual(show_network_res.status_int, 200)
75 network_data = Serializer().deserialize(show_network_res.body,75 network_data = Serializer().deserialize(show_network_res.body,
76 content_type)76 content_type)
77 self.assertEqual(network_id,77 self.assertEqual(network_id, network_data['network']['id'])
78 network_data['network']['id'])
79 LOG.debug("_test_create_network - format:%s - END", format)78 LOG.debug("_test_create_network - format:%s - END", format)
8079
81 def _test_create_network_badrequest(self, format):80 def _test_create_network_badrequest(self, format):
@@ -102,6 +101,25 @@
102 self.assertEqual(len(network_data['networks']), 2)101 self.assertEqual(len(network_data['networks']), 2)
103 LOG.debug("_test_list_networks - format:%s - END", format)102 LOG.debug("_test_list_networks - format:%s - END", format)
104103
104 def _test_list_networks_detail(self, format):
105 LOG.debug("_test_list_networks_detail - format:%s - START", format)
106 content_type = "application/%s" % format
107 self._create_network(format, "net_1")
108 self._create_network(format, "net_2")
109 list_network_req = testlib.network_list_detail_request(self.tenant_id,
110 format)
111 list_network_res = list_network_req.get_response(self.api)
112 self.assertEqual(list_network_res.status_int, 200)
113 network_data = self._net_serializer.deserialize(
114 list_network_res.body, content_type)
115 # Check network count: should return 2
116 self.assertEqual(len(network_data['networks']), 2)
117 # Check contents - id & name for each network
118 for network in network_data['networks']:
119 self.assertTrue('id' in network and 'name' in network)
120 self.assertTrue(network['id'] and network['name'])
121 LOG.debug("_test_list_networks_detail - format:%s - END", format)
122
105 def _test_show_network(self, format):123 def _test_show_network(self, format):
106 LOG.debug("_test_show_network - format:%s - START", format)124 LOG.debug("_test_show_network - format:%s - START", format)
107 content_type = "application/%s" % format125 content_type = "application/%s" % format
@@ -118,6 +136,25 @@
118 network_data['network'])136 network_data['network'])
119 LOG.debug("_test_show_network - format:%s - END", format)137 LOG.debug("_test_show_network - format:%s - END", format)
120138
139 def _test_show_network_detail(self, format):
140 LOG.debug("_test_show_network_detail - format:%s - START", format)
141 content_type = "application/%s" % format
142 # Create a network and a port
143 network_id = self._create_network(format)
144 port_id = self._create_port(network_id, "ACTIVE", format)
145 show_network_req = testlib.show_network_detail_request(
146 self.tenant_id, network_id, format)
147 show_network_res = show_network_req.get_response(self.api)
148 self.assertEqual(show_network_res.status_int, 200)
149 network_data = self._net_serializer.deserialize(
150 show_network_res.body, content_type)
151 self.assertEqual({'id': network_id,
152 'name': self.network_name,
153 'ports': [{'id': port_id,
154 'state': 'ACTIVE'}]},
155 network_data['network'])
156 LOG.debug("_test_show_network_detail - format:%s - END", format)
157
121 def _test_show_network_not_found(self, format):158 def _test_show_network_not_found(self, format):
122 LOG.debug("_test_show_network_not_found - format:%s - START", format)159 LOG.debug("_test_show_network_not_found - format:%s - START", format)
123 show_network_req = testlib.show_network_request(self.tenant_id,160 show_network_req = testlib.show_network_request(self.tenant_id,
@@ -137,7 +174,7 @@
137 new_name,174 new_name,
138 format)175 format)
139 update_network_res = update_network_req.get_response(self.api)176 update_network_res = update_network_req.get_response(self.api)
140 self.assertEqual(update_network_res.status_int, 202)177 self.assertEqual(update_network_res.status_int, 204)
141 show_network_req = testlib.show_network_request(self.tenant_id,178 show_network_req = testlib.show_network_request(self.tenant_id,
142 network_id,179 network_id,
143 format)180 format)
@@ -187,7 +224,7 @@
187 network_id,224 network_id,
188 format)225 format)
189 delete_network_res = delete_network_req.get_response(self.api)226 delete_network_res = delete_network_req.get_response(self.api)
190 self.assertEqual(delete_network_res.status_int, 202)227 self.assertEqual(delete_network_res.status_int, 204)
191 list_network_req = testlib.network_list_request(self.tenant_id,228 list_network_req = testlib.network_list_request(self.tenant_id,
192 format)229 format)
193 list_network_res = list_network_req.get_response(self.api)230 list_network_res = list_network_req.get_response(self.api)
@@ -213,7 +250,7 @@
213 port_id,250 port_id,
214 attachment_id)251 attachment_id)
215 attachment_res = attachment_req.get_response(self.api)252 attachment_res = attachment_req.get_response(self.api)
216 self.assertEquals(attachment_res.status_int, 202)253 self.assertEquals(attachment_res.status_int, 204)
217254
218 LOG.debug("Deleting network %(network_id)s"\255 LOG.debug("Deleting network %(network_id)s"\
219 " of tenant %(tenant_id)s", locals())256 " of tenant %(tenant_id)s", locals())
@@ -241,6 +278,27 @@
241 self.assertEqual(len(port_data['ports']), 2)278 self.assertEqual(len(port_data['ports']), 2)
242 LOG.debug("_test_list_ports - format:%s - END", format)279 LOG.debug("_test_list_ports - format:%s - END", format)
243280
281 def _test_list_ports_detail(self, format):
282 LOG.debug("_test_list_ports_detail - format:%s - START", format)
283 content_type = "application/%s" % format
284 port_state = "ACTIVE"
285 network_id = self._create_network(format)
286 self._create_port(network_id, port_state, format)
287 self._create_port(network_id, port_state, format)
288 list_port_req = testlib.port_list_detail_request(self.tenant_id,
289 network_id, format)
290 list_port_res = list_port_req.get_response(self.api)
291 self.assertEqual(list_port_res.status_int, 200)
292 port_data = self._port_serializer.deserialize(
293 list_port_res.body, content_type)
294 # Check port count: should return 2
295 self.assertEqual(len(port_data['ports']), 2)
296 # Check contents - id & name for each network
297 for port in port_data['ports']:
298 self.assertTrue('id' in port and 'state' in port)
299 self.assertTrue(port['id'] and port['state'])
300 LOG.debug("_test_list_ports_detail - format:%s - END", format)
301
244 def _test_show_port(self, format):302 def _test_show_port(self, format):
245 LOG.debug("_test_show_port - format:%s - START", format)303 LOG.debug("_test_show_port - format:%s - START", format)
246 content_type = "application/%s" % format304 content_type = "application/%s" % format
@@ -258,6 +316,44 @@
258 port_data['port'])316 port_data['port'])
259 LOG.debug("_test_show_port - format:%s - END", format)317 LOG.debug("_test_show_port - format:%s - END", format)
260318
319 def _test_show_port_detail(self, format):
320 LOG.debug("_test_show_port - format:%s - START", format)
321 content_type = "application/%s" % format
322 port_state = "ACTIVE"
323 network_id = self._create_network(format)
324 port_id = self._create_port(network_id, port_state, format)
325
326 # Part 1 - no attachment
327 show_port_req = testlib.show_port_detail_request(self.tenant_id,
328 network_id, port_id, format)
329 show_port_res = show_port_req.get_response(self.api)
330 self.assertEqual(show_port_res.status_int, 200)
331 port_data = self._port_serializer.deserialize(
332 show_port_res.body, content_type)
333 self.assertEqual({'id': port_id, 'state': port_state},
334 port_data['port'])
335
336 # Part 2 - plug attachment into port
337 interface_id = "test_interface"
338 put_attachment_req = testlib.put_attachment_request(self.tenant_id,
339 network_id,
340 port_id,
341 interface_id,
342 format)
343 put_attachment_res = put_attachment_req.get_response(self.api)
344 self.assertEqual(put_attachment_res.status_int, 204)
345 show_port_req = testlib.show_port_detail_request(self.tenant_id,
346 network_id, port_id, format)
347 show_port_res = show_port_req.get_response(self.api)
348 self.assertEqual(show_port_res.status_int, 200)
349 port_data = self._port_serializer.deserialize(
350 show_port_res.body, content_type)
351 self.assertEqual({'id': port_id, 'state': port_state,
352 'attachment': {'id': interface_id}},
353 port_data['port'])
354
355 LOG.debug("_test_show_port_detail - format:%s - END", format)
356
261 def _test_show_port_networknotfound(self, format):357 def _test_show_port_networknotfound(self, format):
262 LOG.debug("_test_show_port_networknotfound - format:%s - START",358 LOG.debug("_test_show_port_networknotfound - format:%s - START",
263 format)359 format)
@@ -343,7 +439,7 @@
343 network_id, port_id,439 network_id, port_id,
344 format)440 format)
345 delete_port_res = delete_port_req.get_response(self.api)441 delete_port_res = delete_port_req.get_response(self.api)
346 self.assertEqual(delete_port_res.status_int, 202)442 self.assertEqual(delete_port_res.status_int, 204)
347 list_port_req = testlib.port_list_request(self.tenant_id, network_id,443 list_port_req = testlib.port_list_request(self.tenant_id, network_id,
348 format)444 format)
349 list_port_res = list_port_req.get_response(self.api)445 list_port_res = list_port_req.get_response(self.api)
@@ -367,7 +463,7 @@
367 port_id,463 port_id,
368 attachment_id)464 attachment_id)
369 attachment_res = attachment_req.get_response(self.api)465 attachment_res = attachment_req.get_response(self.api)
370 self.assertEquals(attachment_res.status_int, 202)466 self.assertEquals(attachment_res.status_int, 204)
371 LOG.debug("Deleting port %(port_id)s for network %(network_id)s"\467 LOG.debug("Deleting port %(port_id)s for network %(network_id)s"\
372 " of tenant %(tenant_id)s", locals())468 " of tenant %(tenant_id)s", locals())
373 delete_port_req = testlib.port_delete_request(self.tenant_id,469 delete_port_req = testlib.port_delete_request(self.tenant_id,
@@ -417,7 +513,7 @@
417 new_port_state,513 new_port_state,
418 format)514 format)
419 update_port_res = update_port_req.get_response(self.api)515 update_port_res = update_port_req.get_response(self.api)
420 self.assertEqual(update_port_res.status_int, 200)516 self.assertEqual(update_port_res.status_int, 204)
421 show_port_req = testlib.show_port_request(self.tenant_id,517 show_port_req = testlib.show_port_request(self.tenant_id,
422 network_id, port_id,518 network_id, port_id,
423 format)519 format)
@@ -427,6 +523,22 @@
427 show_port_res.body, content_type)523 show_port_res.body, content_type)
428 self.assertEqual({'id': port_id, 'state': new_port_state},524 self.assertEqual({'id': port_id, 'state': new_port_state},
429 port_data['port'])525 port_data['port'])
526 # now set it back to the original value
527 update_port_req = testlib.update_port_request(self.tenant_id,
528 network_id, port_id,
529 port_state,
530 format)
531 update_port_res = update_port_req.get_response(self.api)
532 self.assertEqual(update_port_res.status_int, 204)
533 show_port_req = testlib.show_port_request(self.tenant_id,
534 network_id, port_id,
535 format)
536 show_port_res = show_port_req.get_response(self.api)
537 self.assertEqual(show_port_res.status_int, 200)
538 port_data = self._port_serializer.deserialize(
539 show_port_res.body, content_type)
540 self.assertEqual({'id': port_id, 'state': port_state},
541 port_data['port'])
430 LOG.debug("_test_set_port_state - format:%s - END", format)542 LOG.debug("_test_set_port_state - format:%s - END", format)
431543
432 def _test_set_port_state_networknotfound(self, format):544 def _test_set_port_state_networknotfound(self, format):
@@ -491,7 +603,7 @@
491 interface_id,603 interface_id,
492 format)604 format)
493 put_attachment_res = put_attachment_req.get_response(self.api)605 put_attachment_res = put_attachment_req.get_response(self.api)
494 self.assertEqual(put_attachment_res.status_int, 202)606 self.assertEqual(put_attachment_res.status_int, 204)
495 get_attachment_req = testlib.get_attachment_request(self.tenant_id,607 get_attachment_req = testlib.get_attachment_request(self.tenant_id,
496 network_id,608 network_id,
497 port_id,609 port_id,
@@ -499,7 +611,7 @@
499 get_attachment_res = get_attachment_req.get_response(self.api)611 get_attachment_res = get_attachment_req.get_response(self.api)
500 attachment_data = Serializer().deserialize(get_attachment_res.body,612 attachment_data = Serializer().deserialize(get_attachment_res.body,
501 content_type)613 content_type)
502 self.assertEqual(attachment_data['attachment'], interface_id)614 self.assertEqual(attachment_data['attachment']['id'], interface_id)
503 LOG.debug("_test_show_attachment - format:%s - END", format)615 LOG.debug("_test_show_attachment - format:%s - END", format)
504616
505 def _test_show_attachment_networknotfound(self, format):617 def _test_show_attachment_networknotfound(self, format):
@@ -544,7 +656,7 @@
544 interface_id,656 interface_id,
545 format)657 format)
546 put_attachment_res = put_attachment_req.get_response(self.api)658 put_attachment_res = put_attachment_req.get_response(self.api)
547 self.assertEqual(put_attachment_res.status_int, 202)659 self.assertEqual(put_attachment_res.status_int, 204)
548 LOG.debug("_test_put_attachment - format:%s - END", format)660 LOG.debug("_test_put_attachment - format:%s - END", format)
549661
550 def _test_put_attachment_networknotfound(self, format):662 def _test_put_attachment_networknotfound(self, format):
@@ -593,13 +705,13 @@
593 interface_id,705 interface_id,
594 format)706 format)
595 put_attachment_res = put_attachment_req.get_response(self.api)707 put_attachment_res = put_attachment_req.get_response(self.api)
596 self.assertEqual(put_attachment_res.status_int, 202)708 self.assertEqual(put_attachment_res.status_int, 204)
597 del_attachment_req = testlib.delete_attachment_request(self.tenant_id,709 del_attachment_req = testlib.delete_attachment_request(self.tenant_id,
598 network_id,710 network_id,
599 port_id,711 port_id,
600 format)712 format)
601 del_attachment_res = del_attachment_req.get_response(self.api)713 del_attachment_res = del_attachment_req.get_response(self.api)
602 self.assertEqual(del_attachment_res.status_int, 202)714 self.assertEqual(del_attachment_res.status_int, 204)
603 LOG.debug("_test_delete_attachment - format:%s - END", format)715 LOG.debug("_test_delete_attachment - format:%s - END", format)
604716
605 def _test_delete_attachment_networknotfound(self, format):717 def _test_delete_attachment_networknotfound(self, format):
@@ -670,6 +782,12 @@
670 def test_list_networks_xml(self):782 def test_list_networks_xml(self):
671 self._test_list_networks('xml')783 self._test_list_networks('xml')
672784
785 def test_list_networks_detail_json(self):
786 self._test_list_networks_detail('json')
787
788 def test_list_networks_detail_xml(self):
789 self._test_list_networks_detail('xml')
790
673 def test_create_network_json(self):791 def test_create_network_json(self):
674 self._test_create_network('json')792 self._test_create_network('json')
675793
@@ -694,6 +812,12 @@
694 def test_show_network_xml(self):812 def test_show_network_xml(self):
695 self._test_show_network('xml')813 self._test_show_network('xml')
696814
815 def test_show_network_detail_json(self):
816 self._test_show_network_detail('json')
817
818 def test_show_network_detail_xml(self):
819 self._test_show_network_detail('xml')
820
697 def test_delete_network_json(self):821 def test_delete_network_json(self):
698 self._test_delete_network('json')822 self._test_delete_network('json')
699823
@@ -730,12 +854,24 @@
730 def test_list_ports_xml(self):854 def test_list_ports_xml(self):
731 self._test_list_ports('xml')855 self._test_list_ports('xml')
732856
857 def test_list_ports_detail_json(self):
858 self._test_list_ports_detail('json')
859
860 def test_list_ports_detail_xml(self):
861 self._test_list_ports_detail('xml')
862
733 def test_show_port_json(self):863 def test_show_port_json(self):
734 self._test_show_port('json')864 self._test_show_port('json')
735865
736 def test_show_port_xml(self):866 def test_show_port_xml(self):
737 self._test_show_port('xml')867 self._test_show_port('xml')
738868
869 def test_show_port_detail_json(self):
870 self._test_show_port_detail('json')
871
872 def test_show_port_detail_xml(self):
873 self._test_show_port_detail('xml')
874
739 def test_show_port_networknotfound_json(self):875 def test_show_port_networknotfound_json(self):
740 self._test_show_port_networknotfound('json')876 self._test_show_port_networknotfound('json')
741877
742878
=== modified file 'tests/unit/test_clientlib.py'
--- tests/unit/test_clientlib.py 2011-08-10 02:15:14 +0000
+++ tests/unit/test_clientlib.py 2011-08-24 11:55:39 +0000
@@ -43,7 +43,7 @@
43 return self.content43 return self.content
4444
45 def status(self):45 def status(self):
46 return status46 return self.status
4747
48 # To test error codes, set the host to 10.0.0.1, and the port to the code48 # To test error codes, set the host to 10.0.0.1, and the port to the code
49 def __init__(self, host, port=9696, key_file="", cert_file=""):49 def __init__(self, host, port=9696, key_file="", cert_file=""):
5050
=== modified file 'tests/unit/testlib_api.py'
--- tests/unit/testlib_api.py 2011-08-01 14:40:29 +0000
+++ tests/unit/testlib_api.py 2011-08-24 11:55:39 +0000
@@ -12,26 +12,45 @@
12 return req12 return req
1313
1414
15def _network_list_request(tenant_id, format='xml', detail=False):
16 method = 'GET'
17 detail_str = detail and '/detail' or ''
18 path = "/tenants/%(tenant_id)s/networks" \
19 "%(detail_str)s.%(format)s" % locals()
20 content_type = "application/%s" % format
21 return create_request(path, None, content_type, method)
22
23
15def network_list_request(tenant_id, format='xml'):24def network_list_request(tenant_id, format='xml'):
25 return _network_list_request(tenant_id, format)
26
27
28def network_list_detail_request(tenant_id, format='xml'):
29 return _network_list_request(tenant_id, format, detail=True)
30
31
32def _show_network_request(tenant_id, network_id, format='xml', detail=False):
16 method = 'GET'33 method = 'GET'
17 path = "/tenants/%(tenant_id)s/networks.%(format)s" % locals()34 detail_str = detail and '/detail' or ''
35 path = "/tenants/%(tenant_id)s/networks" \
36 "/%(network_id)s%(detail_str)s.%(format)s" % locals()
18 content_type = "application/%s" % format37 content_type = "application/%s" % format
19 return create_request(path, None, content_type, method)38 return create_request(path, None, content_type, method)
2039
2140
22def show_network_request(tenant_id, network_id, format='xml'):41def show_network_request(tenant_id, network_id, format='xml'):
23 method = 'GET'42 return _show_network_request(tenant_id, network_id, format)
24 path = "/tenants/%(tenant_id)s/networks" \43
25 "/%(network_id)s.%(format)s" % locals()44
26 content_type = "application/%s" % format45def show_network_detail_request(tenant_id, network_id, format='xml'):
27 return create_request(path, None, content_type, method)46 return _show_network_request(tenant_id, network_id, format, detail=True)
2847
2948
30def new_network_request(tenant_id, network_name='new_name',49def new_network_request(tenant_id, network_name='new_name',
31 format='xml', custom_req_body=None):50 format='xml', custom_req_body=None):
32 method = 'POST'51 method = 'POST'
33 path = "/tenants/%(tenant_id)s/networks.%(format)s" % locals()52 path = "/tenants/%(tenant_id)s/networks.%(format)s" % locals()
34 data = custom_req_body or {'network': {'net-name': '%s' % network_name}}53 data = custom_req_body or {'network': {'name': '%s' % network_name}}
35 content_type = "application/%s" % format54 content_type = "application/%s" % format
36 body = Serializer().serialize(data, content_type)55 body = Serializer().serialize(data, content_type)
37 return create_request(path, body, content_type, method)56 return create_request(path, body, content_type, method)
@@ -42,7 +61,7 @@
42 method = 'PUT'61 method = 'PUT'
43 path = "/tenants/%(tenant_id)s/networks" \62 path = "/tenants/%(tenant_id)s/networks" \
44 "/%(network_id)s.%(format)s" % locals()63 "/%(network_id)s.%(format)s" % locals()
45 data = custom_req_body or {'network': {'net-name': '%s' % network_name}}64 data = custom_req_body or {'network': {'name': '%s' % network_name}}
46 content_type = "application/%s" % format65 content_type = "application/%s" % format
47 body = Serializer().serialize(data, content_type)66 body = Serializer().serialize(data, content_type)
48 return create_request(path, body, content_type, method)67 return create_request(path, body, content_type, method)
@@ -56,20 +75,41 @@
56 return create_request(path, None, content_type, method)75 return create_request(path, None, content_type, method)
5776
5877
78def _port_list_request(tenant_id, network_id, format='xml', detail=False):
79 method = 'GET'
80 detail_str = detail and '/detail' or ''
81 path = "/tenants/%(tenant_id)s/networks/" \
82 "%(network_id)s/ports%(detail_str)s.%(format)s" % locals()
83 content_type = "application/%s" % format
84 return create_request(path, None, content_type, method)
85
86
59def port_list_request(tenant_id, network_id, format='xml'):87def port_list_request(tenant_id, network_id, format='xml'):
88 return _port_list_request(tenant_id, network_id, format)
89
90
91def port_list_detail_request(tenant_id, network_id, format='xml'):
92 return _port_list_request(tenant_id, network_id,
93 format, detail=True)
94
95
96def _show_port_request(tenant_id, network_id, port_id,
97 format='xml', detail=False):
60 method = 'GET'98 method = 'GET'
61 path = "/tenants/%(tenant_id)s/networks/" \99 detail_str = detail and '/detail' or ''
62 "%(network_id)s/ports.%(format)s" % locals()100 path = "/tenants/%(tenant_id)s/networks/%(network_id)s" \
101 "/ports/%(port_id)s%(detail_str)s.%(format)s" % locals()
63 content_type = "application/%s" % format102 content_type = "application/%s" % format
64 return create_request(path, None, content_type, method)103 return create_request(path, None, content_type, method)
65104
66105
67def show_port_request(tenant_id, network_id, port_id, format='xml'):106def show_port_request(tenant_id, network_id, port_id, format='xml'):
68 method = 'GET'107 return _show_port_request(tenant_id, network_id, port_id, format)
69 path = "/tenants/%(tenant_id)s/networks/%(network_id)s" \108
70 "/ports/%(port_id)s.%(format)s" % locals()109
71 content_type = "application/%s" % format110def show_port_detail_request(tenant_id, network_id, port_id, format='xml'):
72 return create_request(path, None, content_type, method)111 return _show_port_request(tenant_id, network_id, port_id,
112 format, detail=True)
73113
74114
75def new_port_request(tenant_id, network_id, port_state,115def new_port_request(tenant_id, network_id, port_state,
@@ -78,7 +118,7 @@
78 path = "/tenants/%(tenant_id)s/networks/" \118 path = "/tenants/%(tenant_id)s/networks/" \
79 "%(network_id)s/ports.%(format)s" % locals()119 "%(network_id)s/ports.%(format)s" % locals()
80 data = custom_req_body or port_state and \120 data = custom_req_body or port_state and \
81 {'port': {'port-state': '%s' % port_state}}121 {'port': {'state': '%s' % port_state}}
82 content_type = "application/%s" % format122 content_type = "application/%s" % format
83 body = data and Serializer().serialize(data, content_type)123 body = data and Serializer().serialize(data, content_type)
84 return create_request(path, body, content_type, method)124 return create_request(path, body, content_type, method)
@@ -97,7 +137,7 @@
97 method = 'PUT'137 method = 'PUT'
98 path = "/tenants/%(tenant_id)s/networks" \138 path = "/tenants/%(tenant_id)s/networks" \
99 "/%(network_id)s/ports/%(port_id)s.%(format)s" % locals()139 "/%(network_id)s/ports/%(port_id)s.%(format)s" % locals()
100 data = custom_req_body or {'port': {'port-state': '%s' % port_state}}140 data = custom_req_body or {'port': {'state': '%s' % port_state}}
101 content_type = "application/%s" % format141 content_type = "application/%s" % format
102 body = Serializer().serialize(data, content_type)142 body = Serializer().serialize(data, content_type)
103 return create_request(path, body, content_type, method)143 return create_request(path, body, content_type, method)
@@ -116,7 +156,7 @@
116 method = 'PUT'156 method = 'PUT'
117 path = "/tenants/%(tenant_id)s/networks/" \157 path = "/tenants/%(tenant_id)s/networks/" \
118 "%(network_id)s/ports/%(port_id)s/attachment.%(format)s" % locals()158 "%(network_id)s/ports/%(port_id)s/attachment.%(format)s" % locals()
119 data = {'port': {'attachment-id': attachment_id}}159 data = {'attachment': {'id': attachment_id}}
120 content_type = "application/%s" % format160 content_type = "application/%s" % format
121 body = Serializer().serialize(data, content_type)161 body = Serializer().serialize(data, content_type)
122 return create_request(path, body, content_type, method)162 return create_request(path, body, content_type, method)

Subscribers

People subscribed via source and target branches