Merge lp:~danwent/neutron/ovs-plugin-fixes into lp:neutron/diablo
- ovs-plugin-fixes
- Merge into diablo
Status: | Merged |
---|---|
Merge reported by: | Somik Behera |
Merged at revision: | not available |
Proposed branch: | lp:~danwent/neutron/ovs-plugin-fixes |
Merge into: | lp:neutron/diablo |
Diff against target: |
547 lines (+239/-110) 10 files modified
quantum/cli.py (+3/-1) quantum/db/api.py (+10/-9) quantum/plugins/openvswitch/README (+7/-2) quantum/plugins/openvswitch/agent/ovs_quantum_agent.py (+42/-43) quantum/plugins/openvswitch/agent/set_external_ids.sh (+0/-15) quantum/plugins/openvswitch/ovs_db.py (+0/-18) quantum/plugins/openvswitch/ovs_models.py (+0/-17) quantum/plugins/openvswitch/ovs_quantum_plugin.ini (+3/-3) quantum/plugins/openvswitch/ovs_quantum_plugin.py (+0/-2) tools/batch_config.py (+174/-0) |
To merge this branch: | bzr merge lp:~danwent/neutron/ovs-plugin-fixes |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Somik Behera | netstack-core | Approve | |
Brad Hall (community) | Approve | ||
Review via email:
|
Commit message
Description of the change
This branch started out just aiming to tweak the OVS plugin so it could run on hypervisors other than XenServer. In the course, I fixed a couple of bugs in the OVS plugin and in the DB models created for the OVS plugin.
I also introduced a new tool called "batch_config.py" that simplifies testing by making it easy to create network topologies using a single CLI command, instead of copying and pasting UUIDs from individual calls.
Other than batch_config.py, there should be no new functionality, only fixes.
This code has been pretty heavily tested with libvirt, and Brad has tested it with XenServer.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
dan wendlandt (danwent) wrote : | # |
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Somik Behera (somikbehera) wrote : | # |
1) There is 1 pep8 violation, trunk is pep8 clean, so we should make sure to get this merge cleaned up too.
./quantum/
2) quantum/cli.py should raise an exception when it cant set attachement
except exc.NoResultFound:
pass
But, this bug was pre-existing, I'll file something for this.
3) Otherwise, everything looks good! If you can just fix the pep8 change, we can get this bug fix merged in!
- 23. By dan wendlandt
-
fix pep8 introduced by trunk merge
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Somik Behera (somikbehera) wrote : | # |
Thanks for fixing that pep8 change that had slipped in!
Got to keep the trunk pep8 clean.
Preview Diff
1 | === modified file 'quantum/cli.py' | |||
2 | --- quantum/cli.py 2011-06-24 18:20:03 +0000 | |||
3 | +++ quantum/cli.py 2011-06-27 23:11:53 +0000 | |||
4 | @@ -240,6 +240,7 @@ | |||
5 | 240 | 240 | ||
6 | 241 | def delete_port(manager, *args): | 241 | def delete_port(manager, *args): |
7 | 242 | tid, nid, pid = args | 242 | tid, nid, pid = args |
8 | 243 | manager.delete_port(tid, nid, pid) | ||
9 | 243 | LOG.info("Deleted Virtual Port:%s " \ | 244 | LOG.info("Deleted Virtual Port:%s " \ |
10 | 244 | "on Virtual Network:%s" % (pid, nid)) | 245 | "on Virtual Network:%s" % (pid, nid)) |
11 | 245 | 246 | ||
12 | @@ -318,7 +319,8 @@ | |||
13 | 318 | output = res.read() | 319 | output = res.read() |
14 | 319 | LOG.debug(output) | 320 | LOG.debug(output) |
15 | 320 | if res.status != 202: | 321 | if res.status != 202: |
17 | 321 | LOG.error("Failed to unplug iface from port \"%s\": %s" % (pid, output)) | 322 | LOG.error("Failed to unplug iface from port \"%s\": %s" % \ |
18 | 323 | (pid, output)) | ||
19 | 322 | return | 324 | return |
20 | 323 | print "Unplugged interface from port:%s on network:%s" % (pid, nid) | 325 | print "Unplugged interface from port:%s on network:%s" % (pid, nid) |
21 | 324 | 326 | ||
22 | 325 | 327 | ||
23 | === modified file 'quantum/db/api.py' | |||
24 | --- quantum/db/api.py 2011-06-08 10:21:47 +0000 | |||
25 | +++ quantum/db/api.py 2011-06-27 23:11:53 +0000 | |||
26 | @@ -72,7 +72,7 @@ | |||
27 | 72 | net = None | 72 | net = None |
28 | 73 | try: | 73 | try: |
29 | 74 | net = session.query(models.Network).\ | 74 | net = session.query(models.Network).\ |
31 | 75 | filter_by(name=name).\ | 75 | filter_by(tenant_id=tenant_id, name=name).\ |
32 | 76 | one() | 76 | one() |
33 | 77 | raise Exception("Network with name \"%s\" already exists" % name) | 77 | raise Exception("Network with name \"%s\" already exists" % name) |
34 | 78 | except exc.NoResultFound: | 78 | except exc.NoResultFound: |
35 | @@ -104,7 +104,7 @@ | |||
36 | 104 | session = get_session() | 104 | session = get_session() |
37 | 105 | try: | 105 | try: |
38 | 106 | res = session.query(models.Network).\ | 106 | res = session.query(models.Network).\ |
40 | 107 | filter_by(name=new_name).\ | 107 | filter_by(tenant_id=tenant_id, name=new_name).\ |
41 | 108 | one() | 108 | one() |
42 | 109 | except exc.NoResultFound: | 109 | except exc.NoResultFound: |
43 | 110 | net = network_get(net_id) | 110 | net = network_get(net_id) |
44 | @@ -156,13 +156,14 @@ | |||
45 | 156 | 156 | ||
46 | 157 | def port_set_attachment(port_id, new_interface_id): | 157 | def port_set_attachment(port_id, new_interface_id): |
47 | 158 | session = get_session() | 158 | session = get_session() |
55 | 159 | ports = None | 159 | ports = [] |
56 | 160 | try: | 160 | if new_interface_id != "": |
57 | 161 | ports = session.query(models.Port).\ | 161 | try: |
58 | 162 | filter_by(interface_id=new_interface_id).\ | 162 | ports = session.query(models.Port).\ |
59 | 163 | all() | 163 | filter_by(interface_id=new_interface_id).\ |
60 | 164 | except exc.NoResultFound: | 164 | all() |
61 | 165 | pass | 165 | except exc.NoResultFound: |
62 | 166 | pass | ||
63 | 166 | if len(ports) == 0: | 167 | if len(ports) == 0: |
64 | 167 | port = port_get(port_id) | 168 | port = port_get(port_id) |
65 | 168 | port.interface_id = new_interface_id | 169 | port.interface_id = new_interface_id |
66 | 169 | 170 | ||
67 | === modified file 'quantum/plugins/openvswitch/README' | |||
68 | --- quantum/plugins/openvswitch/README 2011-06-09 14:42:19 +0000 | |||
69 | +++ quantum/plugins/openvswitch/README 2011-06-27 23:11:53 +0000 | |||
70 | @@ -62,20 +62,25 @@ | |||
71 | 62 | distribution tarball (see below) and the agent will use the credentials here | 62 | distribution tarball (see below) and the agent will use the credentials here |
72 | 63 | to access the database. | 63 | to access the database. |
73 | 64 | 64 | ||
75 | 65 | # -- Agent configuration | 65 | # -- XenServer Agent configuration |
76 | 66 | 66 | ||
77 | 67 | - Create the agent distribution tarball | 67 | - Create the agent distribution tarball |
78 | 68 | 68 | ||
79 | 69 | $ make agent-dist | 69 | $ make agent-dist |
80 | 70 | - Copy the resulting tarball to your xenserver(s) (copy to dom0, not the nova | 70 | - Copy the resulting tarball to your xenserver(s) (copy to dom0, not the nova |
81 | 71 | compute node) | 71 | compute node) |
83 | 72 | - Unpack the tarball and run install.sh. This will install all of the | 72 | - Unpack the tarball and run xenserver_install.sh. This will install all of the |
84 | 73 | necessary pieces into /etc/xapi.d/plugins. It will also spit out the name | 73 | necessary pieces into /etc/xapi.d/plugins. It will also spit out the name |
85 | 74 | of the integration bridge that you'll need for your nova configuration. | 74 | of the integration bridge that you'll need for your nova configuration. |
86 | 75 | Make sure to specify this in your nova flagfile as --flat_network_bridge. | 75 | Make sure to specify this in your nova flagfile as --flat_network_bridge. |
87 | 76 | - Run the agent [on your hypervisor (dom0)]: | 76 | - Run the agent [on your hypervisor (dom0)]: |
88 | 77 | $ /etc/xapi.d/plugins/ovs_quantum_agent.py /etc/xapi.d/plugins/ovs_quantum_plugin.ini | 77 | $ /etc/xapi.d/plugins/ovs_quantum_agent.py /etc/xapi.d/plugins/ovs_quantum_plugin.ini |
89 | 78 | 78 | ||
90 | 79 | # -- KVM Agent configuration | ||
91 | 80 | |||
92 | 81 | - Copy ovs_quantum_agent.py and ovs_quantum_plugin.ini to the Linux host and run: | ||
93 | 82 | $ python ovs_quantum_agent.py ovs_quantum_plugin.ini | ||
94 | 83 | |||
95 | 79 | # -- Getting quantum up and running | 84 | # -- Getting quantum up and running |
96 | 80 | 85 | ||
97 | 81 | - Start quantum [on the quantum service host]: | 86 | - Start quantum [on the quantum service host]: |
98 | 82 | 87 | ||
99 | === modified file 'quantum/plugins/openvswitch/agent/ovs_quantum_agent.py' | |||
100 | --- quantum/plugins/openvswitch/agent/ovs_quantum_agent.py 2011-06-08 10:21:47 +0000 | |||
101 | +++ quantum/plugins/openvswitch/agent/ovs_quantum_agent.py 2011-06-27 23:11:53 +0000 | |||
102 | @@ -130,40 +130,40 @@ | |||
103 | 130 | def get_port_stats(self, port_name): | 130 | def get_port_stats(self, port_name): |
104 | 131 | return self.db_get_map("Interface", port_name, "statistics") | 131 | return self.db_get_map("Interface", port_name, "statistics") |
105 | 132 | 132 | ||
106 | 133 | # this is a hack that should go away once nova properly reports bindings | ||
107 | 134 | # to quantum. We have this here for now as it lets us work with | ||
108 | 135 | # unmodified nova | ||
109 | 136 | def xapi_get_port(self, name): | ||
110 | 137 | external_ids = self.db_get_map("Interface", name, "external_ids") | ||
111 | 138 | if "attached-mac" not in external_ids: | ||
112 | 139 | return None | ||
113 | 140 | vm_uuid = external_ids.get("xs-vm-uuid", "") | ||
114 | 141 | if len(vm_uuid) == 0: | ||
115 | 142 | return None | ||
116 | 143 | LOG.debug("iface-id not set, got xs-vm-uuid: %s" % vm_uuid) | ||
117 | 144 | res = os.popen("xe vm-list uuid=%s params=name-label --minimal" \ | ||
118 | 145 | % vm_uuid).readline().strip() | ||
119 | 146 | if len(res) == 0: | ||
120 | 147 | return None | ||
121 | 148 | external_ids["iface-id"] = res | ||
122 | 149 | LOG.info("Setting interface \"%s\" iface-id to \"%s\"" % (name, res)) | ||
123 | 150 | self.set_db_attribute("Interface", name, | ||
124 | 151 | "external-ids:iface-id", res) | ||
125 | 152 | ofport = self.db_get_val("Interface", name, "ofport") | ||
126 | 153 | return VifPort(name, ofport, external_ids["iface-id"], | ||
127 | 154 | external_ids["attached-mac"], self) | ||
128 | 155 | |||
129 | 133 | # returns a VIF object for each VIF port | 156 | # returns a VIF object for each VIF port |
130 | 134 | def get_vif_ports(self): | 157 | def get_vif_ports(self): |
131 | 135 | edge_ports = [] | 158 | edge_ports = [] |
132 | 136 | port_names = self.get_port_name_list() | 159 | port_names = self.get_port_name_list() |
133 | 137 | for name in port_names: | 160 | for name in port_names: |
134 | 138 | external_ids = self.db_get_map("Interface", name, "external_ids") | 161 | external_ids = self.db_get_map("Interface", name, "external_ids") |
163 | 139 | if "iface-id" in external_ids and "attached-mac" in external_ids: | 162 | if "xs-vm-uuid" in external_ids: |
164 | 140 | ofport = self.db_get_val("Interface", name, "ofport") | 163 | p = xapi_get_port(name) |
165 | 141 | p = VifPort(name, ofport, external_ids["iface-id"], | 164 | if p is not None: |
166 | 142 | external_ids["attached-mac"], self) | 165 | edge_ports.append(p) |
167 | 143 | edge_ports.append(p) | 166 | elif "iface-id" in external_ids and "attached-mac" in external_ids: |
140 | 144 | else: | ||
141 | 145 | # iface-id might not be set. See if we can figure it out and | ||
142 | 146 | # set it here. | ||
143 | 147 | external_ids = self.db_get_map("Interface", name, | ||
144 | 148 | "external_ids") | ||
145 | 149 | if "attached-mac" not in external_ids: | ||
146 | 150 | continue | ||
147 | 151 | vif_uuid = external_ids.get("xs-vif-uuid", "") | ||
148 | 152 | if len(vif_uuid) == 0: | ||
149 | 153 | continue | ||
150 | 154 | LOG.debug("iface-id not set, got vif-uuid: %s" % vif_uuid) | ||
151 | 155 | res = os.popen("xe vif-param-get param-name=other-config " | ||
152 | 156 | "uuid=%s | grep nicira-iface-id | " | ||
153 | 157 | "awk '{print $2}'" | ||
154 | 158 | % vif_uuid).readline() | ||
155 | 159 | res = res.strip() | ||
156 | 160 | if len(res) == 0: | ||
157 | 161 | continue | ||
158 | 162 | external_ids["iface-id"] = res | ||
159 | 163 | LOG.info("Setting interface \"%s\" iface-id to \"%s\"" | ||
160 | 164 | % (name, res)) | ||
161 | 165 | self.set_db_attribute("Interface", name, | ||
162 | 166 | "external-ids:iface-id", res) | ||
168 | 167 | ofport = self.db_get_val("Interface", name, "ofport") | 167 | ofport = self.db_get_val("Interface", name, "ofport") |
169 | 168 | p = VifPort(name, ofport, external_ids["iface-id"], | 168 | p = VifPort(name, ofport, external_ids["iface-id"], |
170 | 169 | external_ids["attached-mac"], self) | 169 | external_ids["attached-mac"], self) |
171 | @@ -171,13 +171,15 @@ | |||
172 | 171 | return edge_ports | 171 | return edge_ports |
173 | 172 | 172 | ||
174 | 173 | 173 | ||
176 | 174 | class OVSNaaSPlugin: | 174 | class OVSQuantumAgent: |
177 | 175 | |||
178 | 175 | def __init__(self, integ_br): | 176 | def __init__(self, integ_br): |
179 | 176 | self.setup_integration_br(integ_br) | 177 | self.setup_integration_br(integ_br) |
180 | 177 | 178 | ||
181 | 178 | def port_bound(self, port, vlan_id): | 179 | def port_bound(self, port, vlan_id): |
182 | 179 | self.int_br.set_db_attribute("Port", port.port_name, "tag", | 180 | self.int_br.set_db_attribute("Port", port.port_name, "tag", |
184 | 180 | str(vlan_id)) | 181 | str(vlan_id)) |
185 | 182 | self.int_br.delete_flows(match="in_port=%s" % port.ofport) | ||
186 | 181 | 183 | ||
187 | 182 | def port_unbound(self, port, still_exists): | 184 | def port_unbound(self, port, still_exists): |
188 | 183 | if still_exists: | 185 | if still_exists: |
189 | @@ -186,13 +188,8 @@ | |||
190 | 186 | def setup_integration_br(self, integ_br): | 188 | def setup_integration_br(self, integ_br): |
191 | 187 | self.int_br = OVSBridge(integ_br) | 189 | self.int_br = OVSBridge(integ_br) |
192 | 188 | self.int_br.remove_all_flows() | 190 | self.int_br.remove_all_flows() |
196 | 189 | # drop all traffic on the 'dead vlan' | 191 | # switch all traffic using L2 learning |
194 | 190 | self.int_br.add_flow(priority=2, match="dl_vlan=4095", actions="drop") | ||
195 | 191 | # switch all other traffic using L2 learning | ||
197 | 192 | self.int_br.add_flow(priority=1, actions="normal") | 192 | self.int_br.add_flow(priority=1, actions="normal") |
198 | 193 | # FIXME send broadcast everywhere, regardless of tenant | ||
199 | 194 | #int_br.add_flow(priority=3, match="dl_dst=ff:ff:ff:ff:ff:ff", | ||
200 | 195 | # actions="normal") | ||
201 | 196 | 193 | ||
202 | 197 | def daemon_loop(self, conn): | 194 | def daemon_loop(self, conn): |
203 | 198 | self.local_vlan_map = {} | 195 | self.local_vlan_map = {} |
204 | @@ -201,7 +198,7 @@ | |||
205 | 201 | 198 | ||
206 | 202 | while True: | 199 | while True: |
207 | 203 | cursor = conn.cursor() | 200 | cursor = conn.cursor() |
209 | 204 | cursor.execute("SELECT * FROM network_bindings") | 201 | cursor.execute("SELECT * FROM ports") |
210 | 205 | rows = cursor.fetchall() | 202 | rows = cursor.fetchall() |
211 | 206 | cursor.close() | 203 | cursor.close() |
212 | 207 | all_bindings = {} | 204 | all_bindings = {} |
213 | @@ -226,22 +223,26 @@ | |||
214 | 226 | else: | 223 | else: |
215 | 227 | # no binding, put him on the 'dead vlan' | 224 | # no binding, put him on the 'dead vlan' |
216 | 228 | self.int_br.set_db_attribute("Port", p.port_name, "tag", | 225 | self.int_br.set_db_attribute("Port", p.port_name, "tag", |
218 | 229 | "4095") | 226 | "4095") |
219 | 227 | self.int_br.add_flow(priority=2, | ||
220 | 228 | match="in_port=%s" % p.ofport, actions="drop") | ||
221 | 229 | |||
222 | 230 | old_b = old_local_bindings.get(p.vif_id, None) | 230 | old_b = old_local_bindings.get(p.vif_id, None) |
223 | 231 | new_b = new_local_bindings.get(p.vif_id, None) | 231 | new_b = new_local_bindings.get(p.vif_id, None) |
224 | 232 | |||
225 | 232 | if old_b != new_b: | 233 | if old_b != new_b: |
226 | 233 | if old_b is not None: | 234 | if old_b is not None: |
227 | 234 | LOG.info("Removing binding to net-id = %s for %s" | 235 | LOG.info("Removing binding to net-id = %s for %s" |
228 | 235 | % (old_b, str(p))) | 236 | % (old_b, str(p))) |
229 | 236 | self.port_unbound(p, True) | 237 | self.port_unbound(p, True) |
230 | 237 | if new_b is not None: | 238 | if new_b is not None: |
231 | 238 | LOG.info("Adding binding to net-id = %s for %s" \ | ||
232 | 239 | % (new_b, str(p))) | ||
233 | 240 | # If we don't have a binding we have to stick it on | 239 | # If we don't have a binding we have to stick it on |
234 | 241 | # the dead vlan | 240 | # the dead vlan |
235 | 242 | vlan_id = vlan_bindings.get(all_bindings[p.vif_id], | 241 | vlan_id = vlan_bindings.get(all_bindings[p.vif_id], |
236 | 243 | "4095") | 242 | "4095") |
237 | 244 | self.port_bound(p, vlan_id) | 243 | self.port_bound(p, vlan_id) |
238 | 244 | LOG.info("Adding binding to net-id = %s " \ | ||
239 | 245 | "for %s on vlan %s" % (new_b, str(p), vlan_id)) | ||
240 | 245 | for vif_id in old_vif_ports.keys(): | 246 | for vif_id in old_vif_ports.keys(): |
241 | 246 | if vif_id not in new_vif_ports: | 247 | if vif_id not in new_vif_ports: |
242 | 247 | LOG.info("Port Disappeared: %s" % vif_id) | 248 | LOG.info("Port Disappeared: %s" % vif_id) |
243 | @@ -251,8 +252,6 @@ | |||
244 | 251 | 252 | ||
245 | 252 | old_vif_ports = new_vif_ports | 253 | old_vif_ports = new_vif_ports |
246 | 253 | old_local_bindings = new_local_bindings | 254 | old_local_bindings = new_local_bindings |
247 | 254 | self.int_br.run_cmd(["bash", | ||
248 | 255 | "/etc/xapi.d/plugins/set_external_ids.sh"]) | ||
249 | 256 | time.sleep(2) | 255 | time.sleep(2) |
250 | 257 | 256 | ||
251 | 258 | if __name__ == "__main__": | 257 | if __name__ == "__main__": |
252 | @@ -291,7 +290,7 @@ | |||
253 | 291 | LOG.info("Connecting to database \"%s\" on %s" % (db_name, db_host)) | 290 | LOG.info("Connecting to database \"%s\" on %s" % (db_name, db_host)) |
254 | 292 | conn = MySQLdb.connect(host=db_host, user=db_user, | 291 | conn = MySQLdb.connect(host=db_host, user=db_user, |
255 | 293 | passwd=db_pass, db=db_name) | 292 | passwd=db_pass, db=db_name) |
257 | 294 | plugin = OVSNaaSPlugin(integ_br) | 293 | plugin = OVSQuantumAgent(integ_br) |
258 | 295 | plugin.daemon_loop(conn) | 294 | plugin.daemon_loop(conn) |
259 | 296 | finally: | 295 | finally: |
260 | 297 | if conn: | 296 | if conn: |
261 | 298 | 297 | ||
262 | === removed file 'quantum/plugins/openvswitch/agent/set_external_ids.sh' | |||
263 | --- quantum/plugins/openvswitch/agent/set_external_ids.sh 2011-06-04 03:59:49 +0000 | |||
264 | +++ quantum/plugins/openvswitch/agent/set_external_ids.sh 1970-01-01 00:00:00 +0000 | |||
265 | @@ -1,15 +0,0 @@ | |||
266 | 1 | #!/bin/sh | ||
267 | 2 | VIFLIST=`xe vif-list params=uuid --minimal | sed s/,/" "/g` | ||
268 | 3 | for VIF_UUID in $VIFLIST; do | ||
269 | 4 | DEVICE_NUM=`xe vif-list params=device uuid=$VIF_UUID --minimal` | ||
270 | 5 | VM_NAME=`xe vif-list params=vm-name-label uuid=$VIF_UUID --minimal` | ||
271 | 6 | NAME="$VM_NAME-eth$DEVICE_NUM" | ||
272 | 7 | echo "Vif: $VIF_UUID is '$NAME'" | ||
273 | 8 | xe vif-param-set uuid=$VIF_UUID other-config:nicira-iface-id="$NAME" | ||
274 | 9 | done | ||
275 | 10 | |||
276 | 11 | ps auxw | grep -v grep | grep ovs-xapi-sync > /dev/null 2>&1 | ||
277 | 12 | if [ $? -eq 0 ]; then | ||
278 | 13 | killall -HUP ovs-xapi-sync | ||
279 | 14 | fi | ||
280 | 15 | |||
281 | 16 | 0 | ||
282 | === renamed file 'quantum/plugins/openvswitch/agent/install.sh' => 'quantum/plugins/openvswitch/agent/xenserver_install.sh' | |||
283 | === modified file 'quantum/plugins/openvswitch/ovs_db.py' | |||
284 | --- quantum/plugins/openvswitch/ovs_db.py 2011-06-08 10:21:47 +0000 | |||
285 | +++ quantum/plugins/openvswitch/ovs_db.py 2011-06-27 23:11:53 +0000 | |||
286 | @@ -56,21 +56,3 @@ | |||
287 | 56 | except exc.NoResultFound: | 56 | except exc.NoResultFound: |
288 | 57 | pass | 57 | pass |
289 | 58 | session.flush() | 58 | session.flush() |
290 | 59 | |||
291 | 60 | |||
292 | 61 | def update_network_binding(netid, ifaceid): | ||
293 | 62 | session = db.get_session() | ||
294 | 63 | # Add to or delete from the bindings table | ||
295 | 64 | if ifaceid == None: | ||
296 | 65 | try: | ||
297 | 66 | binding = session.query(ovs_models.NetworkBinding).\ | ||
298 | 67 | filter_by(network_id=netid).\ | ||
299 | 68 | one() | ||
300 | 69 | session.delete(binding) | ||
301 | 70 | except exc.NoResultFound: | ||
302 | 71 | raise Exception("No binding found with network_id = %s" % netid) | ||
303 | 72 | else: | ||
304 | 73 | binding = ovs_models.NetworkBinding(netid, ifaceid) | ||
305 | 74 | session.add(binding) | ||
306 | 75 | |||
307 | 76 | session.flush() | ||
308 | 77 | 59 | ||
309 | === modified file 'quantum/plugins/openvswitch/ovs_models.py' | |||
310 | --- quantum/plugins/openvswitch/ovs_models.py 2011-06-08 10:21:47 +0000 | |||
311 | +++ quantum/plugins/openvswitch/ovs_models.py 2011-06-27 23:11:53 +0000 | |||
312 | @@ -26,23 +26,6 @@ | |||
313 | 26 | from quantum.db.models import BASE | 26 | from quantum.db.models import BASE |
314 | 27 | 27 | ||
315 | 28 | 28 | ||
316 | 29 | class NetworkBinding(BASE): | ||
317 | 30 | """Represents a binding of network_id, vif_id""" | ||
318 | 31 | __tablename__ = 'network_bindings' | ||
319 | 32 | |||
320 | 33 | id = Column(Integer, primary_key=True, autoincrement=True) | ||
321 | 34 | network_id = Column(String(255)) | ||
322 | 35 | vif_id = Column(String(255)) | ||
323 | 36 | |||
324 | 37 | def __init__(self, network_id, vif_id): | ||
325 | 38 | self.network_id = network_id | ||
326 | 39 | self.vif_id = vif_id | ||
327 | 40 | |||
328 | 41 | def __repr__(self): | ||
329 | 42 | return "<NetworkBinding(%s,%s)>" % \ | ||
330 | 43 | (self.network_id, self.vif_id) | ||
331 | 44 | |||
332 | 45 | |||
333 | 46 | class VlanBinding(BASE): | 29 | class VlanBinding(BASE): |
334 | 47 | """Represents a binding of network_id, vlan_id""" | 30 | """Represents a binding of network_id, vlan_id""" |
335 | 48 | __tablename__ = 'vlan_bindings' | 31 | __tablename__ = 'vlan_bindings' |
336 | 49 | 32 | ||
337 | === modified file 'quantum/plugins/openvswitch/ovs_quantum_plugin.ini' | |||
338 | --- quantum/plugins/openvswitch/ovs_quantum_plugin.ini 2011-06-04 03:56:32 +0000 | |||
339 | +++ quantum/plugins/openvswitch/ovs_quantum_plugin.ini 2011-06-27 23:11:53 +0000 | |||
340 | @@ -1,9 +1,9 @@ | |||
341 | 1 | [DATABASE] | 1 | [DATABASE] |
343 | 2 | name = ovs_naas | 2 | name = ovs_quantum |
344 | 3 | user = root | 3 | user = root |
346 | 4 | pass = foobar | 4 | pass = nova |
347 | 5 | host = 127.0.0.1 | 5 | host = 127.0.0.1 |
348 | 6 | port = 3306 | 6 | port = 3306 |
349 | 7 | 7 | ||
350 | 8 | [OVS] | 8 | [OVS] |
352 | 9 | integration-bridge = xapi1 | 9 | integration-bridge = br100 |
353 | 10 | 10 | ||
354 | === modified file 'quantum/plugins/openvswitch/ovs_quantum_plugin.py' | |||
355 | --- quantum/plugins/openvswitch/ovs_quantum_plugin.py 2011-06-08 10:21:47 +0000 | |||
356 | +++ quantum/plugins/openvswitch/ovs_quantum_plugin.py 2011-06-27 23:11:53 +0000 | |||
357 | @@ -200,11 +200,9 @@ | |||
358 | 200 | 200 | ||
359 | 201 | def plug_interface(self, tenant_id, net_id, port_id, remote_iface_id): | 201 | def plug_interface(self, tenant_id, net_id, port_id, remote_iface_id): |
360 | 202 | db.port_set_attachment(port_id, remote_iface_id) | 202 | db.port_set_attachment(port_id, remote_iface_id) |
361 | 203 | ovs_db.update_network_binding(net_id, remote_iface_id) | ||
362 | 204 | 203 | ||
363 | 205 | def unplug_interface(self, tenant_id, net_id, port_id): | 204 | def unplug_interface(self, tenant_id, net_id, port_id): |
364 | 206 | db.port_set_attachment(port_id, "") | 205 | db.port_set_attachment(port_id, "") |
365 | 207 | ovs_db.update_network_binding(net_id, None) | ||
366 | 208 | 206 | ||
367 | 209 | def get_interface_details(self, tenant_id, net_id, port_id): | 207 | def get_interface_details(self, tenant_id, net_id, port_id): |
368 | 210 | res = db.port_get(port_id) | 208 | res = db.port_get(port_id) |
369 | 211 | 209 | ||
370 | === added file 'tools/batch_config.py' | |||
371 | --- tools/batch_config.py 1970-01-01 00:00:00 +0000 | |||
372 | +++ tools/batch_config.py 2011-06-27 23:11:53 +0000 | |||
373 | @@ -0,0 +1,174 @@ | |||
374 | 1 | # vim: tabstop=4 shiftwidth=4 softtabstop=4 | ||
375 | 2 | |||
376 | 3 | # Copyright 2011 Nicira Networks, Inc. | ||
377 | 4 | # | ||
378 | 5 | # Licensed under the Apache License, Version 2.0 (the "License"); you may | ||
379 | 6 | # not use this file except in compliance with the License. You may obtain | ||
380 | 7 | # a copy of the License at | ||
381 | 8 | # | ||
382 | 9 | # http://www.apache.org/licenses/LICENSE-2.0 | ||
383 | 10 | # | ||
384 | 11 | # Unless required by applicable law or agreed to in writing, software | ||
385 | 12 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||
386 | 13 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||
387 | 14 | # License for the specific language governing permissions and limitations | ||
388 | 15 | # under the License. | ||
389 | 16 | # @author: Dan Wendlandt, Nicira Networks, Inc. | ||
390 | 17 | |||
391 | 18 | import httplib | ||
392 | 19 | import logging as LOG | ||
393 | 20 | import json | ||
394 | 21 | import socket | ||
395 | 22 | import sys | ||
396 | 23 | import urllib | ||
397 | 24 | |||
398 | 25 | from quantum.manager import QuantumManager | ||
399 | 26 | from optparse import OptionParser | ||
400 | 27 | from quantum.common.wsgi import Serializer | ||
401 | 28 | from quantum.cli import MiniClient | ||
402 | 29 | |||
403 | 30 | FORMAT = "json" | ||
404 | 31 | CONTENT_TYPE = "application/" + FORMAT | ||
405 | 32 | |||
406 | 33 | |||
407 | 34 | def delete_all_nets(client, tenant_id): | ||
408 | 35 | res = client.do_request(tenant_id, 'GET', "/networks." + FORMAT) | ||
409 | 36 | resdict = json.loads(res.read()) | ||
410 | 37 | LOG.debug(resdict) | ||
411 | 38 | for n in resdict["networks"]: | ||
412 | 39 | nid = n["id"] | ||
413 | 40 | |||
414 | 41 | res = client.do_request(tenant_id, 'GET', | ||
415 | 42 | "/networks/%s/ports.%s" % (nid, FORMAT)) | ||
416 | 43 | output = res.read() | ||
417 | 44 | if res.status != 200: | ||
418 | 45 | LOG.error("Failed to list ports: %s" % output) | ||
419 | 46 | continue | ||
420 | 47 | rd = json.loads(output) | ||
421 | 48 | LOG.debug(rd) | ||
422 | 49 | for port in rd["ports"]: | ||
423 | 50 | pid = port["id"] | ||
424 | 51 | |||
425 | 52 | data = {'port': {'attachment-id': ''}} | ||
426 | 53 | body = Serializer().serialize(data, CONTENT_TYPE) | ||
427 | 54 | res = client.do_request(tenant_id, 'DELETE', | ||
428 | 55 | "/networks/%s/ports/%s/attachment.%s" % \ | ||
429 | 56 | (nid, pid, FORMAT), body=body) | ||
430 | 57 | output = res.read() | ||
431 | 58 | LOG.debug(output) | ||
432 | 59 | if res.status != 202: | ||
433 | 60 | LOG.error("Failed to unplug iface from port \"%s\": %s" % (vid, | ||
434 | 61 | pid, output)) | ||
435 | 62 | continue | ||
436 | 63 | LOG.info("Unplugged interface from port:%s on network:%s" % (pid, | ||
437 | 64 | nid)) | ||
438 | 65 | |||
439 | 66 | res = client.do_request(tenant_id, 'DELETE', | ||
440 | 67 | "/networks/%s/ports/%s.%s" % (nid, pid, FORMAT)) | ||
441 | 68 | output = res.read() | ||
442 | 69 | if res.status != 202: | ||
443 | 70 | LOG.error("Failed to delete port: %s" % output) | ||
444 | 71 | continue | ||
445 | 72 | print "Deleted Virtual Port:%s " \ | ||
446 | 73 | "on Virtual Network:%s" % (pid, nid) | ||
447 | 74 | |||
448 | 75 | res = client.do_request(tenant_id, 'DELETE', | ||
449 | 76 | "/networks/" + nid + "." + FORMAT) | ||
450 | 77 | status = res.status | ||
451 | 78 | if status != 202: | ||
452 | 79 | Log.error("Failed to delete network: %s" % nid) | ||
453 | 80 | output = res.read() | ||
454 | 81 | print output | ||
455 | 82 | else: | ||
456 | 83 | print "Deleted Virtual Network with ID:%s" % nid | ||
457 | 84 | |||
458 | 85 | |||
459 | 86 | def create_net_with_attachments(net_name, iface_ids): | ||
460 | 87 | data = {'network': {'network-name': '%s' % net_name}} | ||
461 | 88 | body = Serializer().serialize(data, CONTENT_TYPE) | ||
462 | 89 | res = client.do_request(tenant_id, 'POST', | ||
463 | 90 | "/networks." + FORMAT, body=body) | ||
464 | 91 | rd = json.loads(res.read()) | ||
465 | 92 | LOG.debug(rd) | ||
466 | 93 | nid = rd["networks"]["network"]["id"] | ||
467 | 94 | print "Created a new Virtual Network %s with ID:%s" % (net_name, nid) | ||
468 | 95 | |||
469 | 96 | for iface_id in iface_ids: | ||
470 | 97 | res = client.do_request(tenant_id, 'POST', | ||
471 | 98 | "/networks/%s/ports.%s" % (nid, FORMAT)) | ||
472 | 99 | output = res.read() | ||
473 | 100 | if res.status != 200: | ||
474 | 101 | LOG.error("Failed to create port: %s" % output) | ||
475 | 102 | continue | ||
476 | 103 | rd = json.loads(output) | ||
477 | 104 | new_port_id = rd["ports"]["port"]["id"] | ||
478 | 105 | print "Created Virtual Port:%s " \ | ||
479 | 106 | "on Virtual Network:%s" % (new_port_id, nid) | ||
480 | 107 | data = {'port': {'attachment-id': '%s' % iface_id}} | ||
481 | 108 | body = Serializer().serialize(data, CONTENT_TYPE) | ||
482 | 109 | res = client.do_request(tenant_id, 'PUT', | ||
483 | 110 | "/networks/%s/ports/%s/attachment.%s" %\ | ||
484 | 111 | (nid, new_port_id, FORMAT), body=body) | ||
485 | 112 | output = res.read() | ||
486 | 113 | LOG.debug(output) | ||
487 | 114 | if res.status != 202: | ||
488 | 115 | LOG.error("Failed to plug iface \"%s\" to port \"%s\": %s" % \ | ||
489 | 116 | (iface_id, new_port_id, output)) | ||
490 | 117 | continue | ||
491 | 118 | print "Plugged interface \"%s\" to port:%s on network:%s" % \ | ||
492 | 119 | (iface_id, new_port_id, nid) | ||
493 | 120 | |||
494 | 121 | if __name__ == "__main__": | ||
495 | 122 | usagestr = "Usage: %prog [OPTIONS] <tenant-id> <config-string> [args]\n" \ | ||
496 | 123 | "Example config-string: net1=instance-1,instance-2"\ | ||
497 | 124 | ":net2=instance-3,instance-4\n" \ | ||
498 | 125 | "This string would create two networks: \n" \ | ||
499 | 126 | "'net1' would have two ports, with iface-ids "\ | ||
500 | 127 | "instance-1 and instance-2 attached\n" \ | ||
501 | 128 | "'net2' would have two ports, with iface-ids"\ | ||
502 | 129 | " instance-3 and instance-4 attached\n" | ||
503 | 130 | parser = OptionParser(usage=usagestr) | ||
504 | 131 | parser.add_option("-H", "--host", dest="host", | ||
505 | 132 | type="string", default="127.0.0.1", help="ip address of api host") | ||
506 | 133 | parser.add_option("-p", "--port", dest="port", | ||
507 | 134 | type="int", default=9696, help="api poort") | ||
508 | 135 | parser.add_option("-s", "--ssl", dest="ssl", | ||
509 | 136 | action="store_true", default=False, help="use ssl") | ||
510 | 137 | parser.add_option("-v", "--verbose", dest="verbose", | ||
511 | 138 | action="store_true", default=False, help="turn on verbose logging") | ||
512 | 139 | parser.add_option("-d", "--delete", dest="delete", | ||
513 | 140 | action="store_true", default=False, \ | ||
514 | 141 | help="delete existing tenants networks") | ||
515 | 142 | |||
516 | 143 | options, args = parser.parse_args() | ||
517 | 144 | |||
518 | 145 | if options.verbose: | ||
519 | 146 | LOG.basicConfig(level=LOG.DEBUG) | ||
520 | 147 | else: | ||
521 | 148 | LOG.basicConfig(level=LOG.WARN) | ||
522 | 149 | |||
523 | 150 | if len(args) < 1: | ||
524 | 151 | parser.print_help() | ||
525 | 152 | help() | ||
526 | 153 | sys.exit(1) | ||
527 | 154 | |||
528 | 155 | nets = {} | ||
529 | 156 | tenant_id = args[0] | ||
530 | 157 | if len(args) > 1: | ||
531 | 158 | config_str = args[1] | ||
532 | 159 | for net_str in config_str.split(":"): | ||
533 | 160 | arr = net_str.split("=") | ||
534 | 161 | net_name = arr[0] | ||
535 | 162 | nets[net_name] = arr[1].split(",") | ||
536 | 163 | |||
537 | 164 | print "nets: %s" % str(nets) | ||
538 | 165 | |||
539 | 166 | client = MiniClient(options.host, options.port, options.ssl) | ||
540 | 167 | |||
541 | 168 | if options.delete: | ||
542 | 169 | delete_all_nets(client, tenant_id) | ||
543 | 170 | |||
544 | 171 | for net_name, iface_ids in nets.items(): | ||
545 | 172 | create_net_with_attachments(net_name, iface_ids) | ||
546 | 173 | |||
547 | 174 | sys.exit(0) |
One comment, you will notice a change that makes it permissible for multiple ports to have the empty string as an attachment. This was because the CLI actually performs an unplug using the empty string:
def api_unplug_ iface(client, *args):
tid, nid, pid = args
data = {'port': {'attachment-id': ''}}
However, I actually think it would be cleaner if this happened by setting attachment-id to a null/none value. We should check with Salvatore on this.