diff --git a/quantum/plugins/midonet/midonet_lib.py b/quantum/plugins/midonet/midonet_lib.py index fbd7a6bdf0d..9ad6b94ccf2 100644 --- a/quantum/plugins/midonet/midonet_lib.py +++ b/quantum/plugins/midonet/midonet_lib.py @@ -20,23 +20,35 @@ # @author: Ryu Ishimoto, Midokura Japan KK +from webob import exc as w_exc + +from quantum.common import exceptions as q_exc from quantum.openstack.common import log as logging LOG = logging.getLogger(__name__) PREFIX = 'OS_SG_' -SUFFIX_IN = '_IN' -SUFFIX_OUT = '_OUT' +NAME_IDENTIFIABLE_PREFIX_LEN = len(PREFIX) + 36 # 36 = length of uuid +OS_FLOATING_IP_RULE_KEY = 'OS_FLOATING_IP' OS_ROUTER_IN_CHAIN_NAME_FORMAT = 'OS_ROUTER_IN_%s' OS_ROUTER_OUT_CHAIN_NAME_FORMAT = 'OS_ROUTER_OUT_%s' -NAME_IDENTIFIABLE_PREFIX_LEN = len(PREFIX) + 36 # 36 = length of uuid +OS_SG_KEY = 'os_sg_rule_id' +OS_TENANT_ROUTER_RULE_KEY = 'OS_TENANT_ROUTER_RULE' +SNAT_RULE = 'SNAT' +SNAT_RULE_PROPERTY = {OS_TENANT_ROUTER_RULE_KEY: SNAT_RULE} +SUFFIX_IN = '_IN' +SUFFIX_OUT = '_OUT' def sg_label(sg_id, sg_name): """Construct the security group ID used as chain identifier in MidoNet.""" return PREFIX + str(sg_id) + '_' + sg_name + +def sg_rule_properties(os_sg_rule_id): + return {OS_SG_KEY: str(os_sg_rule_id)} + port_group_name = sg_label @@ -48,18 +60,748 @@ def chain_names(sg_id, sg_name): return {'in': in_chain_name, 'out': out_chain_name} -class ChainManager: +def router_chain_names(router_id): + in_name = OS_ROUTER_IN_CHAIN_NAME_FORMAT % router_id + out_name = OS_ROUTER_OUT_CHAIN_NAME_FORMAT % router_id + return {'in': in_name, 'out': out_name} + + +def handle_api_error(fn): + def wrapped(*args, **kwargs): + try: + return fn(*args, **kwargs) + except w_exc.HTTPException as ex: + raise MidonetApiException(msg=ex) + return wrapped + + +class MidonetResourceNotFound(q_exc.NotFound): + message = _('MidoNet %(resource_type)s %(id)s could not be found') + + +class MidonetApiException(q_exc.QuantumException): + message = _("MidoNet API error: %(msg)s") + + +class MidoClient: def __init__(self, mido_api): self.mido_api = mido_api + @handle_api_error + def create_bridge(self, tenant_id, name): + """Create a new bridge + + :param tenant_id: id of tenant creating the bridge + :param name: name of the bridge + :returns: newly created bridge + """ + LOG.debug(_("MidoClient.create_bridge called: " + "tenant_id=%(tenant_id)s, name=%(name)s"), + {'tenant_id': tenant_id, 'name': name}) + return self.mido_api.add_bridge().name(name).tenant_id( + tenant_id).create() + + @handle_api_error + def delete_bridge(self, id): + """Delete a bridge + + :param id: id of the bridge + """ + LOG.debug(_("MidoClient.delete_bridge called: id=%(id)s"), {'id': id}) + return self.mido_api.delete_bridge(id) + + @handle_api_error + def get_bridge(self, id): + """Get a bridge + + :param id: id of the bridge + :returns: requested bridge. None if bridge does not exist. + """ + LOG.debug(_("MidoClient.get_bridge called: id=%s"), id) + try: + return self.mido_api.get_bridge(id) + except w_exc.HTTPNotFound: + raise MidonetResourceNotFound(resource_type='Bridge', id=id) + + @handle_api_error + def update_bridge(self, id, name): + """Update a bridge of the given id with the new name + + :param id: id of the bridge + :param name: name of the bridge to set to + :returns: bridge object + """ + LOG.debug(_("MidoClient.update_bridge called: " + "id=%(id)s, name=%(name)s"), {'id': id, 'name': name}) + try: + return self.mido_api.get_bridge(id).name(name).update() + except w_exc.HTTPNotFound: + raise MidonetResourceNotFound(resource_type='Bridge', id=id) + + @handle_api_error + def create_dhcp(self, bridge, gateway_ip, net_addr, net_len): + """Create a new DHCP entry + + :param bridge: bridge object to add dhcp to + :param gateway_ip: IP address of gateway + :param net_addr: network IP address + :param net_len: network IP address length + :returns: newly created dhcp + """ + LOG.debug(_("MidoClient.create_dhcp called: bridge=%s(bridge)s, " + "net_addr=%(net_addr)s, net_len=%(net_len)s, " + "gateway_ip=%(gateway_ip)s"), + {'bridge': bridge, 'net_addr': net_addr, 'net_len': net_len, + 'gateway_ip': gateway_ip}) + return bridge.add_dhcp_subnet().default_gateway( + gateway_ip).subnet_prefix(net_addr).subnet_length( + net_len).create() + + @handle_api_error + def create_dhcp_hosts(self, bridge, ip, mac): + """Create DHCP host entries + + :param bridge: bridge of the DHCP + :param ip: IP address + :param mac: MAC address + """ + LOG.debug(_("MidoClient.create_dhcp_hosts called: bridge=%s(bridge), " + "ip=%(ip)s, mac=%(mac)s"), {'bridge': bridge, 'ip': ip, + 'mac': mac}) + dhcp_subnets = bridge.get_dhcp_subnets() + if dhcp_subnets: + # Add the host to the first subnet as we currently support one + # subnet per network. + dhcp_subnets[0].add_dhcp_host().ip_addr(ip).mac_addr(mac).create() + + @handle_api_error + def delete_dhcp_hosts(self, bridge_id, ip, mac): + """Delete DHCP host entries + + :param bridge_id: id of the bridge of the DHCP + :param ip: IP address + :param mac: MAC address + """ + LOG.debug(_("MidoClient.delete_dhcp_hosts called: " + "bridge_id=%s(bridge_id), ip=%(ip)s, mac=%(mac)s"), + {'bridge_id': bridge_id, 'ip': ip, 'mac': mac}) + bridge = self.get_bridge(bridge_id) + dhcp_subnets = bridge.get_dhcp_subnets() + if dhcp_subnets: + for dh in dhcp_subnets[0].get_dhcp_hosts(): + if dh.get_mac_addr() == mac and dh.get_ip_addr() == ip: + dh.delete() + + @handle_api_error + def delete_dhcp(self, bridge): + """Delete a DHCP entry + + :param bridge: bridge to remove DHCP from + """ + LOG.debug(_("MidoClient.delete_dhcp called: bridge=%s(bridge), "), + {'bridge': bridge}) + dhcp = bridge.get_dhcp_subnets() + if not dhcp: + raise MidonetApiException(msg="Tried to delete non-existent DHCP") + dhcp[0].delete() + + @handle_api_error + def delete_port(self, id): + """Delete a port + + :param id: id of the port + """ + LOG.debug(_("MidoClient.delete_port called: id=%(id)s"), {'id': id}) + self.mido_api.delete_port(id) + + @handle_api_error + def get_port(self, id): + """Get a port + + :param id: id of the port + :returns: requested port. None if it does not exist + """ + LOG.debug(_("MidoClient.get_port called: id=%(id)s"), {'id': id}) + try: + return self.mido_api.get_port(id) + except w_exc.HTTPNotFound: + raise MidonetResourceNotFound(resource_type='Port', id=id) + + @handle_api_error + def create_exterior_bridge_port(self, bridge): + """Create a new exterior bridge port + + :param bridge: bridge object to add port to + :returns: newly created port + """ + LOG.debug(_("MidoClient.create_exterior_bridge_port called: " + "bridge=%(bridge)s"), {'bridge': bridge}) + return bridge.add_exterior_port().create() + + @handle_api_error + def create_interior_bridge_port(self, bridge): + """Create a new interior bridge port + + :param bridge: bridge object to add port to + :returns: newly created port + """ + LOG.debug(_("MidoClient.create_interior_bridge_port called: " + "bridge=%(bridge)s"), {'bridge': bridge}) + return bridge.add_interior_port().create() + + @handle_api_error + def create_router(self, tenant_id, name): + """Create a new router + + :param tenant_id: id of tenant creating the router + :param name: name of the router + :returns: newly created router + """ + LOG.debug(_("MidoClient.create_router called: " + "tenant_id=%(tenant_id)s, name=%(name)s"), + {'tenant_id': tenant_id, 'name': name}) + return self.mido_api.add_router().name(name).tenant_id( + tenant_id).create() + + @handle_api_error + def create_tenant_router(self, tenant_id, name, metadata_router): + """Create a new tenant router + + :param tenant_id: id of tenant creating the router + :param name: name of the router + :param metadata_router: metadata router + :returns: newly created router + """ + LOG.debug(_("MidoClient.create_tenant_router called: " + "tenant_id=%(tenant_id)s, name=%(name)s" + "metadata_router=%(metadata_router)s"), + {'tenant_id': tenant_id, 'name': name, + 'metadata_router': metadata_router}) + router = self.create_router(tenant_id, name) + self.link_router_to_metadata_router(router, metadata_router) + return router + + @handle_api_error + def delete_tenant_router(self, id, metadata_router): + """Delete a tenant router + + :param id: id of router + :param metadata_router: metadata router + """ + LOG.debug(_("MidoClient.delete_tenant_router called: " + "id=%(id)s, metadata_router=%(metadata_router)s"), + {'id': id, 'metadata_router': metadata_router}) + self.unlink_router_from_metadata_router(id, metadata_router) + self.destroy_router_chains(id) + + # delete the router + self.delete_router(id) + + @handle_api_error + def delete_router(self, id): + """Delete a router + + :param id: id of the router + """ + LOG.debug(_("MidoClient.delete_router called: id=%(id)s"), {'id': id}) + return self.mido_api.delete_router(id) + + @handle_api_error + def get_router(self, id): + """Get a router with the given id + + :param id: id of the router + :returns: requested router object. None if it does not exist. + """ + LOG.debug(_("MidoClient.get_router called: id=%(id)s"), {'id': id}) + try: + return self.mido_api.get_router(id) + except w_exc.HTTPNotFound: + raise MidonetResourceNotFound(resource_type='Router', id=id) + + @handle_api_error + def update_router(self, id, name): + """Update a router of the given id with the new name + + :param id: id of the router + :param name: name of the router to set to + :returns: router object + """ + LOG.debug(_("MidoClient.update_router called: " + "id=%(id)s, name=%(name)s"), {'id': id, 'name': name}) + try: + return self.mido_api.get_router(id).name(name).update() + except w_exc.HTTPNotFound: + raise MidonetResourceNotFound(resource_type='Router', id=id) + + @handle_api_error + def link_bridge_port_to_router(self, port_id, router_id, gateway_ip, + net_addr, net_len, metadata_router): + """Link a tenant bridge port to the router + + :param port_id: port ID + :param router_id: router id to link to + :param gateway_ip: IP address of gateway + :param net_addr: network IP address + :param net_len: network IP address length + :param metadata_router: metadata router instance + """ + LOG.debug(_("MidoClient.link_bridge_port_to_router called: " + "port_id=%(port_id)s, router_id=%(router_id)s, " + "gateway_ip=%(gateway_ip)s net_addr=%(net_addr)s, " + "net_len=%(net_len)s, " + "metadata_router=%(metadata_router)s"), + {'port_id': port_id, 'router_id': router_id, + 'gateway_ip': gateway_ip, 'net_addr': net_addr, + 'net_len': net_len, 'metadata_router': metadata_router}) + router = self.get_router(router_id) + + # create an interior port on the router + in_port = router.add_interior_port() + router_port = in_port.port_address(gateway_ip).network_address( + net_addr).network_length(net_len).create() + + br_port = self.get_port(port_id) + router_port.link(br_port.get_id()) + + # add a route for the subnet in the provider router + router.add_route().type('Normal').src_network_addr( + '0.0.0.0').src_network_length(0).dst_network_addr( + net_addr).dst_network_length(net_len).weight( + 100).next_hop_port(router_port.get_id()).create() + + # add a route for the subnet in metadata router; forward + # packets destined to the subnet to the tenant router + for pp in metadata_router.get_peer_ports(): + if pp.get_device_id() == router.get_id(): + mdr_port_id = pp.get_peer_id() + break + else: + raise Exception( + _("Couldn't find a md router port for the router=%r"), router) + + metadata_router.add_route().type('Normal').src_network_addr( + '0.0.0.0').src_network_length(0).dst_network_addr( + net_addr).dst_network_length(net_len).weight( + 100).next_hop_port(mdr_port_id).create() + + @handle_api_error + def unlink_bridge_port_from_router(self, port_id, net_addr, net_len, + metadata_router): + """Unlink a tenant bridge port from the router + + :param bridge_id: bridge ID + :param net_addr: network IP address + :param net_len: network IP address length + :param metadata_router: metadata router instance + """ + LOG.debug(_("MidoClient.unlink_bridge_port_from_router called: " + "port_id=%(port_id)s, net_addr=%(net_addr)s, " + "net_len=%(net_len)s, " + "metadata_router=%(metadata_router)s"), + {'port_id': port_id, 'net_addr': net_addr, + 'net_len': net_len, 'metadata_router': metadata_router}) + port = self.get_port(port_id) + port.unlink() + self.delete_port(port.get_peer_id()) + self.delete_port(port.get_id()) + + # delete the route for the subnet in the metadata router + for r in metadata_router.get_routes(): + if (r.get_dst_network_addr() == net_addr and + r.get_dst_network_length() == net_len): + LOG.debug(_('Deleting route=%r ...'), r) + self.mido_api.delete_route(r.get_id()) + break + + @handle_api_error + def link_bridge_to_provider_router(self, bridge, provider_router, + gateway_ip, net_addr, net_len): + """Link a tenant bridge to the provider router + + :param bridge: tenant bridge + :param provider_router: provider router to link to + :param gateway_ip: IP address of gateway + :param net_addr: network IP address + :param net_len: network IP address length + """ + LOG.debug(_("MidoClient.link_bridge_to_provider_router called: " + "bridge=%(bridge)s, provider_router=%(provider_router)s, " + "gateway_ip=%(gateway_ip)s, net_addr=%(net_addr)s, " + "net_len=%(net_len)s"), + {'bridge': bridge, 'provider_router': provider_router, + 'gateway_ip': gateway_ip, 'net_addr': net_addr, + 'net_len': net_len}) + # create an interior port on the provider router + in_port = provider_router.add_interior_port() + pr_port = in_port.port_address(gateway_ip).network_address( + net_addr).network_length(net_len).create() + + # create an interior bridge port, then link it to the router. + br_port = bridge.add_interior_port().create() + pr_port.link(br_port.get_id()) + + # add a route for the subnet in the provider router + provider_router.add_route().type('Normal').src_network_addr( + '0.0.0.0').src_network_length(0).dst_network_addr( + net_addr).dst_network_length(net_len).weight( + 100).next_hop_port(pr_port.get_id()).create() + + @handle_api_error + def unlink_bridge_from_provider_router(self, bridge, provider_router): + """Unlink a tenant bridge from the provider router + + :param bridge: tenant bridge + :param provider_router: provider router to link to + """ + LOG.debug(_("MidoClient.unlink_bridge_from_provider_router called: " + "bridge=%(bridge)s, provider_router=%(provider_router)s"), + {'bridge': bridge, 'provider_router': provider_router}) + # Delete routes and unlink the router and the bridge. + routes = provider_router.get_routes() + + bridge_ports_to_delete = [ + p for p in provider_router.get_peer_ports() + if p.get_device_id() == bridge.get_id()] + + for p in bridge.get_peer_ports(): + if p.get_device_id() == provider_router.get_id(): + # delete the routes going to the bridge + for r in routes: + if r.get_next_hop_port() == p.get_id(): + self.mido_api.delete_route(r.get_id()) + p.unlink() + self.mido_api.delete_port(p.get_id()) + + # delete bridge port + for port in bridge_ports_to_delete: + self.mido_api.delete_port(port.get_id()) + + @handle_api_error + def set_router_external_gateway(self, id, provider_router, snat_ip): + """Set router external gateway + + :param ID: ID of the tenant router + :param provider_router: provider router + :param snat_ip: SNAT IP address + """ + LOG.debug(_("MidoClient.set_router_external_gateway called: " + "id=%(id)s, provider_router=%(provider_router)s, " + "snat_ip=%s(snat_ip)s)"), + {'id': id, 'provider_router': provider_router, + 'snat_ip': snat_ip}) + tenant_router = self.get_router(id) + + # Create a interior port in the provider router + in_port = provider_router.add_interior_port() + pr_port = in_port.network_address( + '169.254.255.0').network_length(30).port_address( + '169.254.255.1').create() + + # Create a port in the tenant router + tr_port = tenant_router.add_interior_port().network_address( + '169.254.255.0').network_length(30).port_address( + '169.254.255.2').create() + + # Link them + pr_port.link(tr_port.get_id()) + + # Add a route for snat_ip to bring it down to tenant + provider_router.add_route().type( + 'Normal').src_network_addr('0.0.0.0').src_network_length( + 0).dst_network_addr(snat_ip).dst_network_length( + 32).weight(100).next_hop_port( + pr_port.get_id()).create() + + # Add default route to uplink in the tenant router + tenant_router.add_route().type('Normal').src_network_addr( + '0.0.0.0').src_network_length(0).dst_network_addr( + '0.0.0.0').dst_network_length(0).weight( + 100).next_hop_port(tr_port.get_id()).create() + + # ADD SNAT(masquerade) rules + chains = self.get_router_chains( + tenant_router.get_tenant_id(), tenant_router.get_id()) + + chains['in'].add_rule().nw_dst_address(snat_ip).nw_dst_length( + 32).type('rev_snat').flow_action('accept').in_ports( + [tr_port.get_id()]).properties( + SNAT_RULE_PROPERTY).position(1).create() + + nat_targets = [] + nat_targets.append( + {'addressFrom': snat_ip, 'addressTo': snat_ip, + 'portFrom': 1, 'portTo': 65535}) + + chains['out'].add_rule().type('snat').flow_action( + 'accept').nat_targets(nat_targets).out_ports( + [tr_port.get_id()]).properties( + SNAT_RULE_PROPERTY).position(1).create() + + @handle_api_error + def clear_router_external_gateway(self, id): + """Clear router external gateway + + :param ID: ID of the tenant router + """ + LOG.debug(_("MidoClient.clear_router_external_gateway called: " + "id=%(id)s"), {'id': id}) + tenant_router = self.get_router(id) + + # delete the port that is connected to provider router + for p in tenant_router.get_ports(): + if p.get_port_address() == '169.254.255.2': + peer_port_id = p.get_peer_id() + p.unlink() + self.mido_api.delete_port(peer_port_id) + self.mido_api.delete_port(p.get_id()) + + # delete default route + for r in tenant_router.get_routes(): + if (r.get_dst_network_addr() == '0.0.0.0' and + r.get_dst_network_length() == 0): + self.mido_api.delete_route(r.get_id()) + + # delete SNAT(masquerade) rules + chains = self.get_router_chains( + tenant_router.get_tenant_id(), + tenant_router.get_id()) + + for r in chains['in'].get_rules(): + if OS_TENANT_ROUTER_RULE_KEY in r.get_properties(): + if r.get_properties()[ + OS_TENANT_ROUTER_RULE_KEY] == SNAT_RULE: + self.mido_api.delete_rule(r.get_id()) + + for r in chains['out'].get_rules(): + if OS_TENANT_ROUTER_RULE_KEY in r.get_properties(): + if r.get_properties()[ + OS_TENANT_ROUTER_RULE_KEY] == SNAT_RULE: + self.mido_api.delete_rule(r.get_id()) + + @handle_api_error + def get_router_chains(self, tenant_id, router_id): + """Get router chains. + + Returns a dictionary that has in/out chain resources key'ed with 'in' + and 'out' respectively, given the tenant_id and the router_id passed + in in the arguments. + """ + LOG.debug(_("MidoClient.get_router_chains called: " + "tenant_id=%(tenant_id)s router_id=%(router_id)s"), + {'tenant_id': tenant_id, 'router_id': router_id}) + + chain_names = router_chain_names(router_id) + chains = {} + for c in self.mido_api.get_chains({'tenant_id': tenant_id}): + if c.get_name() == chain_names['in']: + chains['in'] = c + elif c.get_name() == chain_names['out']: + chains['out'] = c + return chains + + @handle_api_error + def create_router_chains(self, router): + """Create chains for a new router. + + Creates chains for the router and returns the same dictionary as + get_router_chains() returns. + + :param router: router to set chains for + """ + LOG.debug(_("MidoClient.create_router_chains called: " + "router=%(router)s"), {'router': router}) + chains = {} + router_id = router.get_id() + tenant_id = router.get_tenant_id() + chain_names = router_chain_names(router_id) + chains['in'] = self.mido_api.add_chain().tenant_id(tenant_id).name( + chain_names['in']).create() + + chains['out'] = self.mido_api.add_chain().tenant_id(tenant_id).name( + chain_names['out']).create() + + # set chains to in/out filters + router.inbound_filter_id( + chains['in'].get_id()).outbound_filter_id( + chains['out'].get_id()).update() + return chains + + @handle_api_error + def destroy_router_chains(self, id): + """Deletes chains of a router. + + :param id: router ID to delete chains of + """ + LOG.debug(_("MidoClient.destroy_router_chains called: " + "id=%(id)s"), {'id': id}) + # delete corresponding chains + router = self.get_router(id) + chains = self.get_router_chains(router.get_tenant_id(), id) + self.mido_api.delete_chain(chains['in'].get_id()) + self.mido_api.delete_chain(chains['out'].get_id()) + + @handle_api_error + def link_router_to_metadata_router(self, router, metadata_router): + """Link a router to the metadata router + + :param router: router to link + :param metadata_router: metadata router + """ + LOG.debug(_("MidoClient.link_router_to_metadata_router called: " + "router=%(router)s, metadata_router=%(metadata_router)s"), + {'router': router, 'metadata_router': metadata_router}) + # link to metadata router + in_port = metadata_router.add_interior_port() + mdr_port = in_port.network_address('169.254.255.0').network_length( + 30).port_address('169.254.255.1').create() + + tr_port = router.add_interior_port().network_address( + '169.254.255.0').network_length(30).port_address( + '169.254.255.2').create() + mdr_port.link(tr_port.get_id()) + + # forward metadata traffic to metadata router + router.add_route().type('Normal').src_network_addr( + '0.0.0.0').src_network_length(0).dst_network_addr( + '169.254.169.254').dst_network_length(32).weight( + 100).next_hop_port(tr_port.get_id()).create() + + @handle_api_error + def unlink_router_from_metadata_router(self, id, metadata_router): + """Unlink a router from the metadata router + + :param id: ID of router + :param metadata_router: metadata router + """ + LOG.debug(_("MidoClient.unlink_router_from_metadata_router called: " + "id=%(id)s, metadata_router=%(metadata_router)s"), + {'id': id, 'metadata_router': metadata_router}) + # unlink from metadata router and delete the interior ports + # that connect metadata router and this router. + for pp in metadata_router.get_peer_ports(): + if pp.get_device_id() == id: + mdr_port = self.get_port(pp.get_peer_id()) + pp.unlink() + self.mido_api.delete_port(pp.get_id()) + self.mido_api.delete_port(mdr_port.get_id()) + + @handle_api_error + def setup_floating_ip(self, router_id, provider_router, floating_ip, + fixed_ip, identifier): + """Setup MidoNet for floating IP + + :param router_id: router_id + :param provider_router: provider router + :param floating_ip: floating IP address + :param fixed_ip: fixed IP address + :param identifier: identifier to use to map to MidoNet + """ + LOG.debug(_("MidoClient.setup_floating_ip called: " + "router_id=%(router_id)s, " + "provider_router=%(provider_router)s" + "floating_ip=%(floating_ip)s, fixed_ip=%(fixed_ip)s" + "identifier=%(identifier)s"), + {'router_id': router_id, 'provider_router': provider_router, + 'floating_ip': floating_ip, 'fixed_ip': fixed_ip, + 'identifier': identifier}) + # unlink from metadata router and delete the interior ports + router = self.mido_api.get_router(router_id) + # find the provider router port that is connected to the tenant + # of the floating ip + for p in router.get_peer_ports(): + if p.get_device_id() == provider_router.get_id(): + pr_port = p + + # get the tenant router port id connected to provider router + tr_port_id = pr_port.get_peer_id() + + # add a route for the floating ip to bring it to the tenant + provider_router.add_route().type( + 'Normal').src_network_addr('0.0.0.0').src_network_length( + 0).dst_network_addr( + floating_ip).dst_network_length( + 32).weight(100).next_hop_port( + pr_port.get_id()).create() + + chains = self.get_router_chains(router.get_tenant_id(), router_id) + + # add dnat/snat rule pair for the floating ip + nat_targets = [] + nat_targets.append( + {'addressFrom': fixed_ip, 'addressTo': fixed_ip, + 'portFrom': 0, 'portTo': 0}) + + floating_property = {OS_FLOATING_IP_RULE_KEY: identifier} + chains['in'].add_rule().nw_dst_address( + floating_ip).nw_dst_length(32).type( + 'dnat').flow_action('accept').nat_targets( + nat_targets).in_ports([tr_port_id]).position( + 1).properties(floating_property).create() + + nat_targets = [] + nat_targets.append( + {'addressFrom': floating_ip, 'addressTo': floating_ip, + 'portFrom': 0, 'portTo': 0}) + + chains['out'].add_rule().nw_src_address( + fixed_ip).nw_src_length(32).type( + 'snat').flow_action('accept').nat_targets( + nat_targets).out_ports( + [tr_port_id]).position(1).properties( + floating_property).create() + + @handle_api_error + def clear_floating_ip(self, router_id, provider_router, floating_ip, + identifier): + """Remove floating IP + + :param router_id: router_id + :param provider_router: provider router + :param floating_ip: floating IP address + :param identifier: identifier to use to map to MidoNet + """ + LOG.debug(_("MidoClient.clear_floating_ip called: " + "router_id=%(router_id)s, " + "provider_router=%(provider_router)s" + "floating_ip=%(floating_ip)s, identifier=%(identifier)s"), + {'router_id': router_id, 'provider_router': provider_router, + 'floating_ip': floating_ip, 'identifier': identifier}) + router = self.mido_api.get_router(router_id) + + # find the provider router port that is connected to the tenant + # delete the route for this floating ip + for r in provider_router.get_routes(): + if (r.get_dst_network_addr() == floating_ip and + r.get_dst_network_length() == 32): + self.mido_api.delete_route(r.get_id()) + + # delete snat/dnat rule pair for this floating ip + chains = self.get_router_chains(router.get_tenant_id(), router_id) + + for r in chains['in'].get_rules(): + if OS_FLOATING_IP_RULE_KEY in r.get_properties(): + if r.get_properties()[OS_FLOATING_IP_RULE_KEY] == identifier: + LOG.debug(_('deleting rule=%r'), r) + self.mido_api.delete_rule(r.get_id()) + break + + for r in chains['out'].get_rules(): + if OS_FLOATING_IP_RULE_KEY in r.get_properties(): + if r.get_properties()[OS_FLOATING_IP_RULE_KEY] == identifier: + LOG.debug(_('deleting rule=%r'), r) + self.mido_api.delete_rule(r.get_id()) + break + + @handle_api_error def create_for_sg(self, tenant_id, sg_id, sg_name): """Create a new chain for security group. Creating a security group creates a pair of chains in MidoNet, one for inbound and the other for outbound. """ - LOG.debug(_("ChainManager.create_for_sg called: " + LOG.debug(_("MidoClient.create_for_sg called: " "tenant_id=%(tenant_id)s sg_id=%(sg_id)s " "sg_name=%(sg_name)s "), {'tenant_id': tenant_id, 'sg_id': sg_id, 'sg_name': sg_name}) @@ -70,13 +812,18 @@ class ChainManager: self.mido_api.add_chain().tenant_id(tenant_id).name( cnames['out']).create() + pg_name = port_group_name(sg_id, sg_name) + self.mido_api.add_port_group().tenant_id(tenant_id).name( + pg_name).create() + + @handle_api_error def delete_for_sg(self, tenant_id, sg_id, sg_name): """Delete a chain mapped to a security group. Delete a SG means deleting all the chains (inbound and outbound) associated with the SG in MidoNet. """ - LOG.debug(_("ChainManager.delete_for_sg called: " + LOG.debug(_("MidoClient.delete_for_sg called: " "tenant_id=%(tenant_id)s sg_id=%(sg_id)s " "sg_name=%(sg_name)s "), {'tenant_id': tenant_id, 'sg_id': sg_id, 'sg_name': sg_name}) @@ -85,52 +832,22 @@ class ChainManager: chains = self.mido_api.get_chains({'tenant_id': tenant_id}) for c in chains: if c.get_name() == cnames['in'] or c.get_name() == cnames['out']: - LOG.debug(_('ChainManager.delete_for_sg: deleting chain=%r'), - c) - c.delete() + LOG.debug(_('MidoClient.delete_for_sg: deleting chain=%r'), + c.get_id()) + self.mido_api.delete_chain(c.get_id()) - def get_router_chains(self, tenant_id, router_id): - """Get router chains. - - Returns a dictionary that has in/out chain resources key'ed with 'in' - and 'out' respectively, given the tenant_id and the router_id passed - in in the arguments. - """ - LOG.debug(_("ChainManager.get_router_chains called: " - "tenant_id=%(tenant_id)s router_id=%(router_id)s"), - {'tenant_id': tenant_id, 'router_id': router_id}) - - router_chain_names = self._get_router_chain_names(router_id) - chains = {} - for c in self.mido_api.get_chains({'tenant_id': tenant_id}): - if c.get_name() == router_chain_names['in']: - chains['in'] = c - elif c.get_name() == router_chain_names['out']: - chains['out'] = c - return chains - - def create_router_chains(self, tenant_id, router_id): - """Create a new chain on a router. - - Creates chains for the router and returns the same dictionary as - get_router_chains() returns. - """ - LOG.debug(_("ChainManager.create_router_chains called: " - "tenant_id=%(tenant_id)s router_id=%(router_id)s"), - {'tenant_id': tenant_id, 'router_id': router_id}) - - chains = {} - router_chain_names = self._get_router_chain_names(router_id) - chains['in'] = self.mido_api.add_chain().tenant_id(tenant_id).name( - router_chain_names['in']).create() - - chains['out'] = self.mido_api.add_chain().tenant_id(tenant_id).name( - router_chain_names['out']).create() - return chains + pg_name = port_group_name(sg_id, sg_name) + pgs = self.mido_api.get_port_groups({'tenant_id': tenant_id}) + for pg in pgs: + if pg.get_name() == pg_name: + LOG.debug(_("MidoClient.delete_for_sg: deleting pg=%r"), + pg) + self.mido_api.delete_port_group(pg.get_id()) + @handle_api_error def get_sg_chains(self, tenant_id, sg_id): """Get a list of chains mapped to a security group.""" - LOG.debug(_("ChainManager.get_sg_chains called: " + LOG.debug(_("MidoClient.get_sg_chains called: " "tenant_id=%(tenant_id)s sg_id=%(sg_id)s"), {'tenant_id': tenant_id, 'sg_id': sg_id}) @@ -148,44 +865,9 @@ class ChainManager: assert 'out' in chains return chains - def _get_router_chain_names(self, router_id): - LOG.debug(_("ChainManager.get_router_chain_names called: " - "router_id=%(router_id)s"), {'router_id': router_id}) - - in_name = OS_ROUTER_IN_CHAIN_NAME_FORMAT % router_id - out_name = OS_ROUTER_OUT_CHAIN_NAME_FORMAT % router_id - router_chain_names = {'in': in_name, 'out': out_name} - return router_chain_names - - -class PortGroupManager: - - def __init__(self, mido_api): - self.mido_api = mido_api - - def create(self, tenant_id, sg_id, sg_name): - LOG.debug(_("PortGroupManager.create called: " - "tenant_id=%(tenant_id)s sg_id=%(sg_id)s " - "sg_name=%(sg_name)s"), - {'tenant_id': tenant_id, 'sg_id': sg_id, 'sg_name': sg_name}) - pg_name = port_group_name(sg_id, sg_name) - self.mido_api.add_port_group().tenant_id(tenant_id).name( - pg_name).create() - - def delete(self, tenant_id, sg_id, sg_name): - LOG.debug(_("PortGroupManager.delete called: " - "tenant_id=%(tenant_id)s sg_id=%(sg_id)s " - "sg_name=%(sg_name)s"), - {'tenant_id': tenant_id, 'sg_id': sg_id, 'sg_name': sg_name}) - pg_name = port_group_name(sg_id, sg_name) - pgs = self.mido_api.get_port_groups({'tenant_id': tenant_id}) - for pg in pgs: - if pg.get_name() == pg_name: - LOG.debug(_("PortGroupManager.delete: deleting pg=%r"), pg) - pg.delete() - - def get_for_sg(self, tenant_id, sg_id): - LOG.debug(_("PortGroupManager.get_for_sg called: " + @handle_api_error + def get_port_groups_for_sg(self, tenant_id, sg_id): + LOG.debug(_("MidoClient.get_port_groups_for_sg called: " "tenant_id=%(tenant_id)s sg_id=%(sg_id)s"), {'tenant_id': tenant_id, 'sg_id': sg_id}) @@ -194,25 +876,14 @@ class PortGroupManager: port_groups = self.mido_api.get_port_groups({'tenant_id': tenant_id}) for pg in port_groups: if pg.get_name().startswith(pg_name_prefix): - LOG.debug(_("PortGroupManager.get_for_sg exiting: pg=%r"), pg) + LOG.debug(_( + "MidoClient.get_port_groups_for_sg exiting: pg=%r"), pg) return pg return None - -class RuleManager: - - OS_SG_KEY = 'os_sg_rule_id' - - def __init__(self, mido_api): - self.mido_api = mido_api - self.chain_manager = ChainManager(mido_api) - self.pg_manager = PortGroupManager(mido_api) - - def _properties(self, os_sg_rule_id): - return {self.OS_SG_KEY: str(os_sg_rule_id)} - + @handle_api_error def create_for_sg_rule(self, rule): - LOG.debug(_("RuleManager.create_for_sg_rule called: rule=%r"), rule) + LOG.debug(_("MidoClient.create_for_sg_rule called: rule=%r"), rule) direction = rule['direction'] protocol = rule['protocol'] @@ -264,7 +935,7 @@ class RuleManager: tp_src_start = tp_src_end = icmp_type tp_dst_start = tp_dst_end = icmp_code - chains = self.chain_manager.get_sg_chains(tenant_id, security_group_id) + chains = self.get_sg_chains(tenant_id, security_group_id) chain = None if direction == 'egress': chain = chains['in'] @@ -274,8 +945,8 @@ class RuleManager: raise Exception(_("Don't know what to do with rule=%r"), rule) # create an accept rule - properties = self._properties(rule_id) - LOG.debug(_("RuleManager.create_for_sg_rule: adding accept rule " + properties = sg_rule_properties(rule_id) + LOG.debug(_("MidoClient.create_for_sg_rule: adding accept rule " "%(rule_id)s in portgroup %(port_group_id)s"), {'rule_id': rule_id, 'port_group_id': port_group_id}) chain.add_rule().port_group(port_group_id).type('accept').nw_proto( @@ -284,20 +955,21 @@ class RuleManager: tp_src_end).tp_dst_start(tp_dst_start).tp_dst_end( tp_dst_end).properties(properties).create() + @handle_api_error def delete_for_sg_rule(self, rule): - LOG.debug(_("RuleManager.delete_for_sg_rule called: rule=%r"), rule) + LOG.debug(_("MidoClient.delete_for_sg_rule called: rule=%r"), rule) tenant_id = rule['tenant_id'] security_group_id = rule['security_group_id'] rule_id = rule['id'] - properties = self._properties(rule_id) + properties = sg_rule_properties(rule_id) # search for the chains to find the rule to delete - chains = self.chain_manager.get_sg_chains(tenant_id, security_group_id) + chains = self.get_sg_chains(tenant_id, security_group_id) for c in chains['in'], chains['out']: rules = c.get_rules() for r in rules: if r.get_properties() == properties: - LOG.debug(_("RuleManager.delete_for_sg_rule: deleting " + LOG.debug(_("MidoClient.delete_for_sg_rule: deleting " "rule %r"), r) - r.delete() + self.mido_api.delete_rule(r.get_id()) diff --git a/quantum/plugins/midonet/plugin.py b/quantum/plugins/midonet/plugin.py index 2c158f2b02e..1c8d3e1186f 100644 --- a/quantum/plugins/midonet/plugin.py +++ b/quantum/plugins/midonet/plugin.py @@ -22,7 +22,6 @@ from midonetclient import api from oslo.config import cfg -from webob import exc as w_exc from quantum.common import exceptions as q_exc from quantum.db import api as db @@ -38,15 +37,6 @@ from quantum.plugins.midonet import midonet_lib LOG = logging.getLogger(__name__) -OS_TENANT_ROUTER_RULE_KEY = 'OS_TENANT_ROUTER_RULE' -OS_FLOATING_IP_RULE_KEY = 'OS_FLOATING_IP' -SNAT_RULE = 'SNAT' -SNAT_RULE_PROPERTY = {OS_TENANT_ROUTER_RULE_KEY: SNAT_RULE} - - -class MidonetResourceNotFound(q_exc.NotFound): - message = _('MidoNet %(resource_type)s %(id)s could not be found') - class MidonetPluginException(q_exc.QuantumException): message = _("%(msg)s") @@ -57,6 +47,7 @@ class MidonetPluginV2(db_base_plugin_v2.QuantumDbPluginV2, securitygroups_db.SecurityGroupDbMixin): supported_extension_aliases = ['router', 'security-group'] + __native_bulk_support = False def __init__(self): @@ -73,26 +64,23 @@ class MidonetPluginV2(db_base_plugin_v2.QuantumDbPluginV2, self.mido_api = api.MidonetApi(midonet_uri, admin_user, admin_pass, project_id=admin_project_id) + self.client = midonet_lib.MidoClient(self.mido_api) - # get MidoNet provider router and metadata router if provider_router_id and metadata_router_id: - self.provider_router = self.mido_api.get_router(provider_router_id) - self.metadata_router = self.mido_api.get_router(metadata_router_id) + # get MidoNet provider router and metadata router + self.provider_router = self.client.get_router(provider_router_id) + self.metadata_router = self.client.get_router(metadata_router_id) - # for dev purpose only - elif mode == 'dev': - msg = _('No provider router and metadata device ids found. ' - 'But skipping because running in dev env.') - LOG.debug(msg) - else: - msg = _('provider_router_id and metadata_router_id ' - 'should be configured in the plugin config file') - LOG.exception(msg) - raise MidonetPluginException(msg=msg) - - self.chain_manager = midonet_lib.ChainManager(self.mido_api) - self.pg_manager = midonet_lib.PortGroupManager(self.mido_api) - self.rule_manager = midonet_lib.RuleManager(self.mido_api) + elif not provider_router_id or not metadata_router_id: + if mode == 'dev': + msg = _('No provider router and metadata device ids found. ' + 'But skipping because running in dev env.') + LOG.debug(msg) + else: + msg = _('provider_router_id and metadata_router_id ' + 'should be configured in the plugin config file') + LOG.exception(msg) + raise MidonetPluginException(msg=msg) db.configure_db() @@ -118,114 +106,27 @@ class MidonetPluginV2(db_base_plugin_v2.QuantumDbPluginV2, with session.begin(subtransactions=True): sn_entry = super(MidonetPluginV2, self).create_subnet(context, subnet) - try: - bridge = self.mido_api.get_bridge(sn_entry['network_id']) - except w_exc.HTTPNotFound: - raise MidonetResourceNotFound(resource_type='Bridge', - id=sn_entry['network_id']) + bridge = self.client.get_bridge(sn_entry['network_id']) gateway_ip = subnet['subnet']['gateway_ip'] network_address, prefix = subnet['subnet']['cidr'].split('/') - bridge.add_dhcp_subnet().default_gateway(gateway_ip).subnet_prefix( - network_address).subnet_length(prefix).create() + self.client.create_dhcp(bridge, gateway_ip, network_address, + prefix) - # If the network is external, link the bridge to MidoNet provider - # router + # For external network, link the bridge to the provider router. self._extend_network_dict_l3(context, net) if net['router:external']: gateway_ip = sn_entry['gateway_ip'] network_address, length = sn_entry['cidr'].split('/') - # create a interior port in the MidoNet provider router - in_port = self.provider_router.add_interior_port() - pr_port = in_port.port_address(gateway_ip).network_address( - network_address).network_length(length).create() - - # create a interior port in the bridge, then link - # it to the provider router. - br_port = bridge.add_interior_port().create() - pr_port.link(br_port.get_id()) - - # add a route for the subnet in the provider router - self.provider_router.add_route().type( - 'Normal').src_network_addr('0.0.0.0').src_network_length( - 0).dst_network_addr( - network_address).dst_network_length( - length).weight(100).next_hop_port( - pr_port.get_id()).create() + self.client.link_bridge_to_provider_router( + bridge, self.provider_router, gateway_ip, network_address, + length) LOG.debug(_("MidonetPluginV2.create_subnet exiting: sn_entry=%r"), sn_entry) return sn_entry - def get_subnet(self, context, id, fields=None): - """Get Quantum subnet. - - Retrieves a Quantum subnet record but also including the DHCP entry - data stored in MidoNet. - """ - LOG.debug(_("MidonetPluginV2.get_subnet called: id=%(id)s " - "fields=%(fields)s"), {'id': id, 'fields': fields}) - - qsubnet = super(MidonetPluginV2, self).get_subnet(context, id) - bridge_id = qsubnet['network_id'] - try: - bridge = self.mido_api.get_bridge(bridge_id) - except w_exc.HTTPNotFound: - raise MidonetResourceNotFound(resource_type='Bridge', - id=bridge_id) - - # get dhcp subnet data from MidoNet bridge. - dhcps = bridge.get_dhcp_subnets() - b_network_address = dhcps[0].get_subnet_prefix() - b_prefix = dhcps[0].get_subnet_length() - - # Validate against quantum database. - network_address, prefix = qsubnet['cidr'].split('/') - if network_address != b_network_address or int(prefix) != b_prefix: - raise MidonetResourceNotFound(resource_type='DhcpSubnet', - id=qsubnet['cidr']) - - LOG.debug(_("MidonetPluginV2.get_subnet exiting: qsubnet=%s"), qsubnet) - return qsubnet - - def get_subnets(self, context, filters=None, fields=None): - """List Quantum subnets. - - Retrieves Quantum subnets with some fields populated by the data - stored in MidoNet. - """ - LOG.debug(_("MidonetPluginV2.get_subnets called: filters=%(filters)r, " - "fields=%(fields)r"), - {'filters': filters, 'fields': fields}) - subnets = super(MidonetPluginV2, self).get_subnets(context, filters, - fields) - for sn in subnets: - if not 'network_id' in sn: - continue - try: - bridge = self.mido_api.get_bridge(sn['network_id']) - except w_exc.HTTPNotFound: - raise MidonetResourceNotFound(resource_type='Bridge', - id=sn['network_id']) - - # TODO(tomoe): dedupe this part. - # get dhcp subnet data from MidoNet bridge. - dhcps = bridge.get_dhcp_subnets() - b_network_address = dhcps[0].get_subnet_prefix() - b_prefix = dhcps[0].get_subnet_length() - - # Validate against quantum database. - if sn.get('cidr'): - network_address, prefix = sn['cidr'].split('/') - if network_address != b_network_address or int( - prefix) != b_prefix: - raise MidonetResourceNotFound(resource_type='DhcpSubnet', - id=sn['cidr']) - - LOG.debug(_("MidonetPluginV2.create_subnet exiting")) - return subnets - def delete_subnet(self, context, id): """Delete Quantum subnet. @@ -237,37 +138,14 @@ class MidonetPluginV2(db_base_plugin_v2.QuantumDbPluginV2, net = super(MidonetPluginV2, self).get_network(context, subnet['network_id'], fields=None) - bridge_id = subnet['network_id'] - try: - bridge = self.mido_api.get_bridge(bridge_id) - except w_exc.HTTPNotFound: - raise MidonetResourceNotFound(resource_type='Bridge', id=bridge_id) - - dhcp = bridge.get_dhcp_subnets() - dhcp[0].delete() + bridge = self.client.get_bridge(subnet['network_id']) + self.client.delete_dhcp(bridge) # If the network is external, clean up routes, links, ports. self._extend_network_dict_l3(context, net) if net['router:external']: - # Delete routes and unlink the router and the bridge. - routes = self.provider_router.get_routes() - - bridge_ports_to_delete = [] - for p in self.provider_router.get_peer_ports(): - if p.get_device_id() == bridge.get_id(): - bridge_ports_to_delete.append(p) - - for p in bridge.get_peer_ports(): - if p.get_device_id() == self.provider_router.get_id(): - # delete the routes going to the brdge - for r in routes: - if r.get_next_hop_port() == p.get_id(): - r.delete() - p.unlink() - p.delete() - - # delete bridge port - map(lambda x: x.delete(), bridge_ports_to_delete) + self.client.unlink_bridge_from_provider_router( + bridge, self.provider_router) super(MidonetPluginV2, self).delete_subnet(context, id) LOG.debug(_("MidonetPluginV2.delete_subnet exiting")) @@ -281,9 +159,8 @@ class MidonetPluginV2(db_base_plugin_v2.QuantumDbPluginV2, network) if network['network']['admin_state_up'] is False: - LOG.warning(_('Ignoring admin_state_up=False for network=%r' - 'Overriding with True'), network) - network['network']['admin_state_up'] = True + LOG.warning(_('Ignoring admin_state_up=False for network=%r ' + 'because it is not yet supported'), network) tenant_id = self._get_tenant_id_for_create(context, network['network']) @@ -291,8 +168,8 @@ class MidonetPluginV2(db_base_plugin_v2.QuantumDbPluginV2, session = context.session with session.begin(subtransactions=True): - bridge = self.mido_api.add_bridge().name( - network['network']['name']).tenant_id(tenant_id).create() + bridge = self.client.create_bridge(tenant_id, + network['network']['name']) # Set MidoNet bridge ID to the quantum DB entry network['network']['id'] = bridge.get_id() @@ -324,11 +201,7 @@ class MidonetPluginV2(db_base_plugin_v2.QuantumDbPluginV2, with session.begin(subtransactions=True): net = super(MidonetPluginV2, self).update_network( context, id, network) - try: - bridge = self.mido_api.get_bridge(id) - except w_exc.HTTPNotFound: - raise MidonetResourceNotFound(resource_type='Bridge', id=id) - bridge.name(net['name']).update() + self.client.update_bridge(id, net['name']) self._extend_network_dict_l3(context, net) LOG.debug(_("MidonetPluginV2.update_network exiting: net=%r"), net) @@ -345,10 +218,7 @@ class MidonetPluginV2(db_base_plugin_v2.QuantumDbPluginV2, # NOTE: Get network data with all fields (fields=None) for # _extend_network_dict_l3() method, which needs 'id' field qnet = super(MidonetPluginV2, self).get_network(context, id, None) - try: - self.mido_api.get_bridge(id) - except w_exc.HTTPNotFound: - raise MidonetResourceNotFound(resource_type='Bridge', id=id) + self.client.get_bridge(id) self._extend_network_dict_l3(context, qnet) LOG.debug(_("MidonetPluginV2.get_network exiting: qnet=%r"), qnet) @@ -364,13 +234,7 @@ class MidonetPluginV2(db_base_plugin_v2.QuantumDbPluginV2, # _extend_network_dict_l3() method, which needs 'id' field qnets = super(MidonetPluginV2, self).get_networks(context, filters, None) - self.mido_api.get_bridges({'tenant_id': context.tenant_id}) for n in qnets: - try: - self.mido_api.get_bridge(n['id']) - except w_exc.HTTPNotFound: - raise MidonetResourceNotFound(resource_type='Bridge', - id=n['id']) self._extend_network_dict_l3(context, n) return [self._fields(net, fields) for net in qnets] @@ -378,8 +242,7 @@ class MidonetPluginV2(db_base_plugin_v2.QuantumDbPluginV2, def delete_network(self, context, id): """Delete a network and its corresponding MidoNet bridge.""" LOG.debug(_("MidonetPluginV2.delete_network called: id=%r"), id) - - self.mido_api.get_bridge(id).delete() + self.client.delete_bridge(id) try: super(MidonetPluginV2, self).delete_network(context, id) except Exception: @@ -394,25 +257,21 @@ class MidonetPluginV2(db_base_plugin_v2.QuantumDbPluginV2, is_compute_interface = False port_data = port['port'] # get the bridge and create a port on it. - try: - bridge = self.mido_api.get_bridge(port_data['network_id']) - except w_exc.HTTPNotFound: - raise MidonetResourceNotFound(resource_type='Bridge', - id=port_data['network_id']) + bridge = self.client.get_bridge(port_data['network_id']) device_owner = port_data['device_owner'] if device_owner.startswith('compute:') or device_owner is '': is_compute_interface = True - bridge_port = bridge.add_exterior_port().create() + bridge_port = self.client.create_exterior_bridge_port(bridge) elif device_owner == l3_db.DEVICE_OWNER_ROUTER_INTF: - bridge_port = bridge.add_interior_port().create() + bridge_port = self.client.create_interior_bridge_port(bridge) elif (device_owner == l3_db.DEVICE_OWNER_ROUTER_GW or device_owner == l3_db.DEVICE_OWNER_FLOATINGIP): # This is a dummy port to make l3_db happy. # This will not be used in MidoNet - bridge_port = bridge.add_interior_port().create() + bridge_port = self.client.create_interior_bridge_port(bridge) if bridge_port: # set midonet port id to quantum port id and create a DB record. @@ -433,20 +292,11 @@ class MidonetPluginV2(db_base_plugin_v2.QuantumDbPluginV2, fixed_ip = port_db_entry['fixed_ips'][0]['ip_address'] mac = port_db_entry['mac_address'] # create dhcp host entry under the bridge. - dhcp_subnets = bridge.get_dhcp_subnets() - if dhcp_subnets: - dhcp_subnets[0].add_dhcp_host().ip_addr( - fixed_ip).mac_addr(mac).create() + self.client.create_dhcp_hosts(bridge, fixed_ip, mac) LOG.debug(_("MidonetPluginV2.create_port exiting: port_db_entry=%r"), port_db_entry) return port_db_entry - def update_port(self, context, id, port): - """Update port.""" - LOG.debug(_("MidonetPluginV2.update_port called: id=%(id)s " - "port=%(port)r"), {'id': id, 'port': port}) - return super(MidonetPluginV2, self).update_port(context, id, port) - def get_port(self, context, id, fields=None): """Retrieve port.""" LOG.debug(_("MidonetPluginV2.get_port called: id=%(id)s " @@ -456,10 +306,7 @@ class MidonetPluginV2(db_base_plugin_v2.QuantumDbPluginV2, port_db_entry = super(MidonetPluginV2, self).get_port(context, id, fields) # verify that corresponding port exists in MidoNet. - try: - self.mido_api.get_port(id) - except w_exc.HTTPNotFound: - raise MidonetResourceNotFound(resource_type='Port', id=id) + self.client.get_port(id) LOG.debug(_("MidonetPluginV2.get_port exiting: port_db_entry=%r"), port_db_entry) @@ -474,12 +321,9 @@ class MidonetPluginV2(db_base_plugin_v2.QuantumDbPluginV2, filters, fields) if ports_db_entry: - try: - for port in ports_db_entry: - self.mido_api.get_port(port['id']) - except w_exc.HTTPNotFound: - raise MidonetResourceNotFound(resource_type='Port', - id=port['id']) + for port in ports_db_entry: + if 'security_gorups' in port: + self._extend_port_dict_security_group(context, port) return ports_db_entry def delete_port(self, context, id, l3_port_check=True): @@ -496,7 +340,6 @@ class MidonetPluginV2(db_base_plugin_v2.QuantumDbPluginV2, with session.begin(subtransactions=True): port_db_entry = super(MidonetPluginV2, self).get_port(context, id, None) - bridge = self.mido_api.get_bridge(port_db_entry['network_id']) # Clean up dhcp host entry if needed. if 'ip_address' in (port_db_entry['fixed_ips'] or [{}])[0]: # get ip and mac from DB record. @@ -504,13 +347,10 @@ class MidonetPluginV2(db_base_plugin_v2.QuantumDbPluginV2, mac = port_db_entry['mac_address'] # create dhcp host entry under the bridge. - dhcp_subnets = bridge.get_dhcp_subnets() - if dhcp_subnets: - for dh in dhcp_subnets[0].get_dhcp_hosts(): - if dh.get_mac_addr() == mac and dh.get_ip_addr() == ip: - dh.delete() + self.client.delete_dhcp_hosts(port_db_entry['network_id'], ip, + mac) - self.mido_api.get_port(id).delete() + self.client.delete_port(id) return super(MidonetPluginV2, self).delete_port(context, id) # @@ -528,40 +368,17 @@ class MidonetPluginV2(db_base_plugin_v2.QuantumDbPluginV2, tenant_id = self._get_tenant_id_for_create(context, router['router']) session = context.session with session.begin(subtransactions=True): - mrouter = self.mido_api.add_router().name( - router['router']['name']).tenant_id(tenant_id).create() + mrouter = self.client.create_tenant_router( + tenant_id, router['router']['name'], self.metadata_router) + qrouter = super(MidonetPluginV2, self).create_router(context, router) - chains = self.chain_manager.create_router_chains(tenant_id, - mrouter.get_id()) - - # set chains to in/out filters - mrouter.inbound_filter_id( - chains['in'].get_id()).outbound_filter_id( - chains['out'].get_id()).update() - # get entry from the DB and update 'id' with MidoNet router id. qrouter_entry = self._get_router(context, qrouter['id']) qrouter['id'] = mrouter.get_id() qrouter_entry.update(qrouter) - # link to metadata router - in_port = self.metadata_router.add_interior_port() - mdr_port = in_port.network_address('169.254.255.0').network_length( - 30).port_address('169.254.255.1').create() - - tr_port = mrouter.add_interior_port().network_address( - '169.254.255.0').network_length(30).port_address( - '169.254.255.2').create() - mdr_port.link(tr_port.get_id()) - - # forward metadata traffic to metadata router - mrouter.add_route().type('Normal').src_network_addr( - '0.0.0.0').src_network_length(0).dst_network_addr( - '169.254.169.254').dst_network_length(32).weight( - 100).next_hop_port(tr_port.get_id()).create() - LOG.debug(_("MidonetPluginV2.create_router exiting: qrouter=%r"), qrouter) return qrouter @@ -603,9 +420,8 @@ class MidonetPluginV2(db_base_plugin_v2.QuantumDbPluginV2, changed_name = router['router'].get('name') if changed_name: - self.mido_api.get_router(id).name(changed_name).update() + self.client.update_router(id, changed_name) - tenant_router = self.mido_api.get_router(id) if op_gateway_set: # find a qport with the network_id for the router qports = super(MidonetPluginV2, self).get_ports( @@ -615,82 +431,12 @@ class MidonetPluginV2(db_base_plugin_v2.QuantumDbPluginV2, qport = qports[0] snat_ip = qport['fixed_ips'][0]['ip_address'] - in_port = self.provider_router.add_interior_port() - pr_port = in_port.network_address( - '169.254.255.0').network_length(30).port_address( - '169.254.255.1').create() - - # Create a port in the tenant router - tr_port = tenant_router.add_interior_port().network_address( - '169.254.255.0').network_length(30).port_address( - '169.254.255.2').create() - - # Link them - pr_port.link(tr_port.get_id()) - - # Add a route for snat_ip to bring it down to tenant - self.provider_router.add_route().type( - 'Normal').src_network_addr('0.0.0.0').src_network_length( - 0).dst_network_addr(snat_ip).dst_network_length( - 32).weight(100).next_hop_port( - pr_port.get_id()).create() - - # Add default route to uplink in the tenant router - tenant_router.add_route().type('Normal').src_network_addr( - '0.0.0.0').src_network_length(0).dst_network_addr( - '0.0.0.0').dst_network_length(0).weight( - 100).next_hop_port(tr_port.get_id()).create() - - # ADD SNAT(masquerade) rules - chains = self.chain_manager.get_router_chains( - tenant_router.get_tenant_id(), tenant_router.get_id()) - - chains['in'].add_rule().nw_dst_address(snat_ip).nw_dst_length( - 32).type('rev_snat').flow_action('accept').in_ports( - [tr_port.get_id()]).properties( - SNAT_RULE_PROPERTY).position(1).create() - - nat_targets = [] - nat_targets.append( - {'addressFrom': snat_ip, 'addressTo': snat_ip, - 'portFrom': 1, 'portTo': 65535}) - - chains['out'].add_rule().type('snat').flow_action( - 'accept').nat_targets(nat_targets).out_ports( - [tr_port.get_id()]).properties( - SNAT_RULE_PROPERTY).position(1).create() + self.client.set_router_external_gateway(id, + self.provider_router, + snat_ip) if op_gateway_clear: - # delete the port that is connected to provider router - for p in tenant_router.get_ports(): - if p.get_port_address() == '169.254.255.2': - peer_port_id = p.get_peer_id() - p.unlink() - self.mido_api.get_port(peer_port_id).delete() - p.delete() - - # delete default route - for r in tenant_router.get_routes(): - if (r.get_dst_network_addr() == '0.0.0.0' and - r.get_dst_network_length() == 0): - r.delete() - - # delete SNAT(masquerade) rules - chains = self.chain_manager.get_router_chains( - tenant_router.get_tenant_id(), - tenant_router.get_id()) - - for r in chains['in'].get_rules(): - if OS_TENANT_ROUTER_RULE_KEY in r.get_properties(): - if r.get_properties()[ - OS_TENANT_ROUTER_RULE_KEY] == SNAT_RULE: - r.delete() - - for r in chains['out'].get_rules(): - if OS_TENANT_ROUTER_RULE_KEY in r.get_properties(): - if r.get_properties()[ - OS_TENANT_ROUTER_RULE_KEY] == SNAT_RULE: - r.delete() + self.client.clear_router_external_gateway(id) LOG.debug(_("MidonetPluginV2.update_router exiting: qrouter=%r"), qrouter) @@ -699,61 +445,13 @@ class MidonetPluginV2(db_base_plugin_v2.QuantumDbPluginV2, def delete_router(self, context, id): LOG.debug(_("MidonetPluginV2.delete_router called: id=%s"), id) - mrouter = self.mido_api.get_router(id) - tenant_id = mrouter.get_tenant_id() - - # unlink from metadata router and delete the interior ports - # that connect metadata router and this router. - for pp in self.metadata_router.get_peer_ports(): - if pp.get_device_id() == mrouter.get_id(): - mdr_port = self.mido_api.get_port(pp.get_peer_id()) - pp.unlink() - pp.delete() - mdr_port.delete() - - # delete corresponding chains - chains = self.chain_manager.get_router_chains(tenant_id, - mrouter.get_id()) - chains['in'].delete() - chains['out'].delete() - - # delete the router - mrouter.delete() + self.client.delete_tenant_router(id) result = super(MidonetPluginV2, self).delete_router(context, id) LOG.debug(_("MidonetPluginV2.delete_router exiting: result=%s"), result) return result - def get_router(self, context, id, fields=None): - LOG.debug(_("MidonetPluginV2.get_router called: id=%(id)s " - "fields=%(fields)r"), {'id': id, 'fields': fields}) - qrouter = super(MidonetPluginV2, self).get_router(context, id, fields) - - try: - self.mido_api.get_router(id) - except w_exc.HTTPNotFound: - raise MidonetResourceNotFound(resource_type='Router', id=id) - - LOG.debug(_("MidonetPluginV2.get_router exiting: qrouter=%r"), - qrouter) - return qrouter - - def get_routers(self, context, filters=None, fields=None): - LOG.debug(_("MidonetPluginV2.get_routers called: filters=%(filters)s " - "fields=%(fields)r"), - {'filters': filters, 'fields': fields}) - - qrouters = super(MidonetPluginV2, self).get_routers( - context, filters, fields) - for qr in qrouters: - try: - self.mido_api.get_router(qr['id']) - except w_exc.HTTPNotFound: - raise MidonetResourceNotFound(resource_type='Router', - id=qr['id']) - return qrouters - def add_router_interface(self, context, router_id, interface_info): LOG.debug(_("MidonetPluginV2.add_router_interface called: " "router_id=%(router_id)s " @@ -772,33 +470,10 @@ class MidonetPluginV2(db_base_plugin_v2.QuantumDbPluginV2, network_address, length = subnet['cidr'].split('/') # Link the router and the bridge port. - mrouter = self.mido_api.get_router(router_id) - mrouter_port = mrouter.add_interior_port().port_address( - gateway_ip).network_address( - network_address).network_length(length).create() - - mbridge_port = self.mido_api.get_port(qport['port_id']) - mrouter_port.link(mbridge_port.get_id()) - - # Add a route entry to the subnet - mrouter.add_route().type('Normal').src_network_addr( - '0.0.0.0').src_network_length(0).dst_network_addr( - network_address).dst_network_length(length).weight( - 100).next_hop_port(mrouter_port.get_id()).create() - - # add a route for the subnet in metadata router; forward - # packets destined to the subnet to the tenant router - found = False - for pp in self.metadata_router.get_peer_ports(): - if pp.get_device_id() == mrouter.get_id(): - mdr_port_id = pp.get_peer_id() - found = True - assert found - - self.metadata_router.add_route().type( - 'Normal').src_network_addr('0.0.0.0').src_network_length( - 0).dst_network_addr(network_address).dst_network_length( - length).weight(100).next_hop_port(mdr_port_id).create() + self.client.link_bridge_port_to_router(qport['port_id'], router_id, + gateway_ip, network_address, + length, + self.metadata_router) LOG.debug(_("MidonetPluginV2.add_router_interface exiting: " "qport=%r"), qport) @@ -810,9 +485,10 @@ class MidonetPluginV2(db_base_plugin_v2.QuantumDbPluginV2, "router_id=%(router_id)s " "interface_info=%(interface_info)r"), {'router_id': router_id, 'interface_info': interface_info}) + port_id = None if 'port_id' in interface_info: - mbridge_port = self.mido_api.get_port(interface_info['port_id']) + port_id = interface_info['port_id'] subnet_id = self.get_port(context, interface_info['port_id'] )['fixed_ips'][0]['subnet_id'] @@ -837,36 +513,18 @@ class MidonetPluginV2(db_base_plugin_v2.QuantumDbPluginV2, network_port = p break assert network_port - mbridge_port = self.mido_api.get_port(network_port['id']) + port_id = network_port['id'] + + assert port_id # get network information from subnet data network_addr, network_length = subnet['cidr'].split('/') network_length = int(network_length) # Unlink the router and the bridge. - mrouter = self.mido_api.get_router(router_id) - mrouter_port = self.mido_api.get_port(mbridge_port.get_peer_id()) - mrouter_port.unlink() - - # Delete the route for the subnet. - found = False - for r in mrouter.get_routes(): - if r.get_next_hop_port() == mrouter_port.get_id(): - r.delete() - found = True - #break # commented out due to issue#314 - assert found - - # delete the route for the subnet in the metadata router - found = False - for r in self.metadata_router.get_routes(): - if (r.get_dst_network_addr() == network_addr and - r.get_dst_network_length() == network_length): - LOG.debug(_('Deleting route=%r ...'), r) - r.delete() - found = True - break - assert found + self.client.unlink_bridge_port_from_router(port_id, network_addr, + network_length, + self.metadata_router) info = super(MidonetPluginV2, self).remove_router_interface( context, router_id, interface_info) @@ -883,91 +541,18 @@ class MidonetPluginV2(db_base_plugin_v2.QuantumDbPluginV2, if floatingip['floatingip']['port_id']: fip = super(MidonetPluginV2, self).update_floatingip( context, id, floatingip) - router_id = fip['router_id'] - floating_address = fip['floating_ip_address'] - fixed_address = fip['fixed_ip_address'] - - tenant_router = self.mido_api.get_router(router_id) - # find the provider router port that is connected to the tenant - # of the floating ip - for p in tenant_router.get_peer_ports(): - if p.get_device_id() == self.provider_router.get_id(): - pr_port = p - - # get the tenant router port id connected to provider router - tr_port_id = pr_port.get_peer_id() - - # add a route for the floating ip to bring it to the tenant - self.provider_router.add_route().type( - 'Normal').src_network_addr('0.0.0.0').src_network_length( - 0).dst_network_addr( - floating_address).dst_network_length( - 32).weight(100).next_hop_port( - pr_port.get_id()).create() - - chains = self.chain_manager.get_router_chains(fip['tenant_id'], - fip['router_id']) - # add dnat/snat rule pair for the floating ip - nat_targets = [] - nat_targets.append( - {'addressFrom': fixed_address, 'addressTo': fixed_address, - 'portFrom': 0, 'portTo': 0}) - - floating_property = {OS_FLOATING_IP_RULE_KEY: id} - chains['in'].add_rule().nw_dst_address( - floating_address).nw_dst_length(32).type( - 'dnat').flow_action('accept').nat_targets( - nat_targets).in_ports([tr_port_id]).position( - 1).properties(floating_property).create() - - nat_targets = [] - nat_targets.append( - {'addressFrom': floating_address, - 'addressTo': floating_address, - 'portFrom': 0, - 'portTo': 0}) - - chains['out'].add_rule().nw_src_address( - fixed_address).nw_src_length(32).type( - 'snat').flow_action('accept').nat_targets( - nat_targets).out_ports( - [tr_port_id]).position(1).properties( - floating_property).create() + self.client.setup_floating_ip(fip['router_id'], + self.provider_router, + fip['floating_ip_address'], + fip['fixed_ip_address'], id) # disassociate floating IP elif floatingip['floatingip']['port_id'] is None: fip = super(MidonetPluginV2, self).get_floatingip(context, id) - - router_id = fip['router_id'] - floating_address = fip['floating_ip_address'] - fixed_address = fip['fixed_ip_address'] - - # delete the route for this floating ip - for r in self.provider_router.get_routes(): - if (r.get_dst_network_addr() == floating_address and - r.get_dst_network_length() == 32): - r.delete() - - # delete snat/dnat rule pair for this floating ip - chains = self.chain_manager.get_router_chains(fip['tenant_id'], - fip['router_id']) - LOG.debug(_('chains=%r'), chains) - - for r in chains['in'].get_rules(): - if OS_FLOATING_IP_RULE_KEY in r.get_properties(): - if r.get_properties()[OS_FLOATING_IP_RULE_KEY] == id: - LOG.debug(_('deleting rule=%r'), r) - r.delete() - break - - for r in chains['out'].get_rules(): - if OS_FLOATING_IP_RULE_KEY in r.get_properties(): - if r.get_properties()[OS_FLOATING_IP_RULE_KEY] == id: - LOG.debug(_('deleting rule=%r'), r) - r.delete() - break - + self.client.clear_floating_ip(fip['router_id'], + self.provider_router, + fip['floating_ip_address'], id) super(MidonetPluginV2, self).update_floatingip(context, id, floatingip) @@ -993,10 +578,8 @@ class MidonetPluginV2(db_base_plugin_v2.QuantumDbPluginV2, context, security_group, default_sg) # Create MidoNet chains and portgroup for the SG - sg_id = sg_db_entry['id'] - sg_name = sg_db_entry['name'] - self.chain_manager.create_for_sg(tenant_id, sg_id, sg_name) - self.pg_manager.create(tenant_id, sg_id, sg_name) + self.client.create_for_sg(tenant_id, sg_db_entry['id'], + sg_db_entry['name']) LOG.debug(_("MidonetPluginV2.create_security_group exiting: " "sg_db_entry=%r"), sg_db_entry) @@ -1026,8 +609,7 @@ class MidonetPluginV2(db_base_plugin_v2.QuantumDbPluginV2, raise ext_sg.SecurityGroupInUse(id=sg_id) # Delete MidoNet Chains and portgroup for the SG - self.chain_manager.delete_for_sg(tenant_id, sg_id, sg_name) - self.pg_manager.delete(tenant_id, sg_id, sg_name) + self.client.delete_for_sg(tenant_id, sg_id, sg_name) return super(MidonetPluginV2, self).delete_security_group( context, id) @@ -1057,7 +639,7 @@ class MidonetPluginV2(db_base_plugin_v2.QuantumDbPluginV2, MidonetPluginV2, self).create_security_group_rule( context, security_group_rule) - self.rule_manager.create_for_sg_rule(rule_db_entry) + self.client.create_for_sg_rule(rule_db_entry) LOG.debug(_("MidonetPluginV2.create_security_group_rule exiting: " "rule_db_entry=%r"), rule_db_entry) return rule_db_entry @@ -1073,7 +655,7 @@ class MidonetPluginV2(db_base_plugin_v2.QuantumDbPluginV2, if not rule_db_entry: raise ext_sg.SecurityGroupRuleNotFound(id=sgrid) - self.rule_manager.delete_for_sg_rule(rule_db_entry) + self.client.delete_for_sg_rule(rule_db_entry) return super(MidonetPluginV2, self).delete_security_group_rule(context, sgrid) diff --git a/quantum/tests/unit/midonet/etc/midonet.ini.test b/quantum/tests/unit/midonet/etc/midonet.ini.test new file mode 100644 index 00000000000..36d715621f8 --- /dev/null +++ b/quantum/tests/unit/midonet/etc/midonet.ini.test @@ -0,0 +1,16 @@ +[MIDONET] + +# MidoNet API server URI +midonet_uri = http://localhost:8080/midonet-api + +# MidoNet admin username +username = admin + +# MidoNet admin password +password = passw0rd + +# Virtual provider router ID +provider_router_id = 00112233-0011-0011-0011-001122334455 + +# Virtual metadata router ID +metadata_router_id = ffeeddcc-ffee-ffee-ffee-ffeeddccbbaa diff --git a/quantum/tests/unit/midonet/mock_lib.py b/quantum/tests/unit/midonet/mock_lib.py new file mode 100644 index 00000000000..8b9224ee49d --- /dev/null +++ b/quantum/tests/unit/midonet/mock_lib.py @@ -0,0 +1,253 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright (C) 2013 Midokura PTE LTD +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# @author: Ryu Ishimoto, Midokura Japan KK + +import mock +import uuid + + +def get_bridge_mock(id=None, tenant_id='test-tenant', name='net'): + if id is None: + id = str(uuid.uuid4()) + + bridge = mock.Mock() + bridge.get_id.return_value = id + bridge.get_tenant_id.return_value = tenant_id + bridge.get_name.return_value = name + return bridge + + +def get_bridge_port_mock(id=None, bridge_id=None, + type='ExteriorBridge'): + if id is None: + id = str(uuid.uuid4()) + if bridge_id is None: + bridge_id = str(uuid.uuid4()) + + port = mock.Mock() + port.get_id.return_value = id + port.get_brige_id.return_value = bridge_id + port.get_type.return_value = type + return port + + +def get_chain_mock(id=None, tenant_id='test-tenant', name='chain', + rules=None): + if id is None: + id = str(uuid.uuid4()) + + if rules is None: + rules = [] + + chain = mock.Mock() + chain.get_id.return_value = id + chain.get_tenant_id.return_value = tenant_id + chain.get_name.return_value = name + chain.get_rules.return_value = rules + return chain + + +def get_exterior_bridge_port_mock(id=None, bridge_id=None): + if id is None: + id = str(uuid.uuid4()) + if bridge_id is None: + bridge_id = str(uuid.uuid4()) + + return get_bridge_port_mock(id=id, bridge_id=bridge_id, + type='ExteriorBridge') + + +def get_interior_bridge_port_mock(id=None, bridge_id=None): + if id is None: + id = str(uuid.uuid4()) + if bridge_id is None: + bridge_id = str(uuid.uuid4()) + + return get_bridge_port_mock(id=id, bridge_id=bridge_id, + type='InteriorBridge') + + +def get_port_group_mock(id=None, tenant_id='test-tenant', name='pg'): + if id is None: + id = str(uuid.uuid4()) + + port_group = mock.Mock() + port_group.get_id.return_value = id + port_group.get_tenant_id.return_value = tenant_id + port_group.get_name.return_value = name + return port_group + + +def get_router_mock(id=None, tenant_id='test-tenant', name='router'): + if id is None: + id = str(uuid.uuid4()) + + router = mock.Mock() + router.get_id.return_value = id + router.get_tenant_id.return_value = tenant_id + router.get_name.return_value = name + return router + + +def get_rule_mock(id=None, chain_id=None, properties=None): + if id is None: + id = str(uuid.uuid4()) + + if chain_id is None: + chain_id = str(uuid.uuid4()) + + if properties is None: + properties = {} + + rule = mock.Mock() + rule.get_id.return_value = id + rule.get_chain_id.return_value = chain_id + rule.get_properties.return_value = properties + return rule + + +def get_subnet_mock(bridge_id=None, gateway_ip='10.0.0.1', + subnet_prefix='10.0.0.0', subnet_len=int(24)): + if bridge_id is None: + bridge_id = str(uuid.uuid4()) + + subnet = mock.Mock() + subnet.get_id.return_value = subnet_prefix + '/' + str(subnet_len) + subnet.get_bridge_id.return_value = bridge_id + subnet.get_default_gateway.return_value = gateway_ip + subnet.get_subnet_prefix.return_value = subnet_prefix + subnet.get_subnet_length.return_value = subnet_len + return subnet + + +class MidonetLibMockConfig(): + + def __init__(self, inst): + self.inst = inst + + def _create_bridge(self, tenant_id, name): + return get_bridge_mock(tenant_id=tenant_id, name=name) + + def _create_exterior_bridge_port(self, bridge): + return get_exterior_bridge_port_mock(bridge_id=bridge.get_id()) + + def _create_interior_bridge_port(self, bridge): + return get_interior_bridge_port_mock(bridge_id=bridge.get_id()) + + def _create_subnet(self, bridge, gateway_ip, subnet_prefix, subnet_len): + return get_subnet_mock(bridge.get_id(), gateway_ip=gateway_ip, + subnet_prefix=subnet_prefix, + subnet_len=subnet_len) + + def _get_bridge(self, id): + return get_bridge_mock(id=id) + + def _get_port(self, id): + return get_exterior_bridge_port_mock(id=id) + + def _get_router(self, id): + return get_router_mock(id=id) + + def _update_bridge(self, id, name): + return get_bridge_mock(id=id, name=name) + + def setup(self): + # Bridge methods side effects + self.inst.create_bridge.side_effect = self._create_bridge + self.inst.get_bridge.side_effect = self._get_bridge + self.inst.update_bridge.side_effect = self._update_bridge + + # Subnet methods side effects + self.inst.create_subnet.side_effect = self._create_subnet + + # Port methods side effects + ex_bp = self.inst.create_exterior_bridge_port + ex_bp.side_effect = self._create_exterior_bridge_port + in_bp = self.inst.create_interior_bridge_port + in_bp.side_effect = self._create_interior_bridge_port + self.inst.get_port.side_effect = self._get_port + + # Router methods side effects + self.inst.get_router.side_effect = self._get_router + + +class MidoClientMockConfig(): + + def __init__(self, inst): + self.inst = inst + self.chains_in = None + self.port_groups_in = None + self.chains_out = None + self.rules_out = None + self.port_groups_out = None + + def _get_query_tenant_id(self, query): + if query is not None and query['tenant_id']: + tenant_id = query['tenant_id'] + else: + tenant_id = 'test-tenant' + return tenant_id + + def _get_bridge(self, id): + return get_bridge_mock(id=id) + + def _get_chains(self, query=None): + if not self.chains_in: + return [] + + tenant_id = self._get_query_tenant_id(query) + self.chains_out = [] + self.rules_out = [] + for chain in self.chains_in: + chain_id = chain['id'] + + rule_mocks = [] + if 'rules' in chain: + for rule in chain['rules']: + rule_mocks.append( + get_rule_mock(id=rule['id'], + chain_id=id, + properties=rule['properties'])) + self.rules_out += rule_mocks + + self.chains_out.append(get_chain_mock(id=chain_id, + name=chain['name'], + tenant_id=tenant_id, + rules=rule_mocks)) + return self.chains_out + + def _get_port_groups(self, query=None): + if not self.port_groups_in: + return [] + + tenant_id = self._get_query_tenant_id(query) + self.port_groups_out = [] + for port_group in self.port_groups_in: + self.port_groups_out.append(get_port_group_mock( + id=port_group['id'], name=port_group['name'], + tenant_id=tenant_id)) + return self.port_groups_out + + def _get_router(self, id): + return get_router_mock(id=id) + + def setup(self): + self.inst.get_bridge.side_effect = self._get_bridge + self.inst.get_chains.side_effect = self._get_chains + self.inst.get_port_groups.side_effect = self._get_port_groups + self.inst.get_router.side_effect = self._get_router diff --git a/quantum/tests/unit/midonet/test_midonet_lib.py b/quantum/tests/unit/midonet/test_midonet_lib.py index cd8dc95fb83..17e35346692 100644 --- a/quantum/tests/unit/midonet/test_midonet_lib.py +++ b/quantum/tests/unit/midonet/test_midonet_lib.py @@ -19,173 +19,201 @@ # @author: Ryu Ishimoto, Midokura Japan KK # @author: Tomoe Sugihara, Midokura Japan KK -import uuid import mock +import testtools +import webob.exc as w_exc +from quantum.openstack.common import uuidutils from quantum.plugins.midonet import midonet_lib -from quantum.tests import base +import quantum.tests.unit.midonet.mock_lib as mock_lib -class MidonetLibTestCase(base.BaseTestCase): +def _create_test_chain(id, name, tenant_id): + return {'id': id, 'name': name, 'tenant_id': tenant_id} + + +def _create_test_port_group(sg_id, sg_name, id, tenant_id): + return {"id": id, "name": "OS_SG_%s_%s" % (sg_id, sg_name), + "tenant_id": tenant_id} + + +def _create_test_router_in_chain(router_id, id, tenant_id): + name = "OS_ROUTER_IN_%s" % router_id + return _create_test_chain(id, name, tenant_id) + + +def _create_test_router_out_chain(router_id, id, tenant_id): + name = "OS_ROUTER_OUT_%s" % router_id + return _create_test_chain(id, name, tenant_id) + + +def _create_test_rule(id, chain_id, properties): + return {"id": id, "chain_id": chain_id, "properties": properties} + + +def _create_test_sg_in_chain(sg_id, sg_name, id, tenant_id): + if sg_name: + name = "OS_SG_%s_%s_IN" % (sg_id, sg_name) + else: + name = "OS_SG_%s_IN" % sg_id + return _create_test_chain(id, name, tenant_id) + + +def _create_test_sg_out_chain(sg_id, sg_name, id, tenant_id): + if sg_name: + name = "OS_SG_%s_%s_OUT" % (sg_id, sg_name) + else: + name = "OS_SG_%s_OUT" % sg_id + return _create_test_chain(id, name, tenant_id) + + +def _create_test_sg_rule(tenant_id, sg_id, id, + direction="egress", protocol="tcp", port_min=1, + port_max=65535, src_ip='192.168.1.0/24', + src_group_id=None, ethertype=0x0800, properties=None): + return {"tenant_id": tenant_id, "security_group_id": sg_id, + "id": id, "direction": direction, "protocol": protocol, + "remote_ip_prefix": src_ip, "remote_group_id": src_group_id, + "port_range_min": port_min, "port_range_max": port_max, + "ethertype": ethertype, "external_id": None} + + +def _create_test_sg_chain_rule(id, chain_id, sg_rule_id): + props = {"os_sg_rule_id": sg_rule_id} + return _create_test_rule(id, chain_id, props) + + +class MidoClientTestCase(testtools.TestCase): def setUp(self): - super(MidonetLibTestCase, self).setUp() + super(MidoClientTestCase, self).setUp() + self._tenant_id = 'test-tenant' self.mock_api = mock.Mock() - - def _create_mock_chains(self, sg_id, sg_name): - mock_in_chain = mock.Mock() - mock_in_chain.get_name.return_value = "OS_SG_%s_%s_IN" % (sg_id, - sg_name) - mock_out_chain = mock.Mock() - mock_out_chain.get_name.return_value = "OS_SG_%s_%s_OUT" % (sg_id, - sg_name) - return (mock_in_chain, mock_out_chain) - - def _create_mock_router_chains(self, router_id): - mock_in_chain = mock.Mock() - mock_in_chain.get_name.return_value = "OS_ROUTER_IN_%s" % (router_id) - - mock_out_chain = mock.Mock() - mock_out_chain.get_name.return_value = "OS_ROUTER_OUT_%s" % (router_id) - return (mock_in_chain, mock_out_chain) - - def _create_mock_port_group(self, sg_id, sg_name): - mock_pg = mock.Mock() - mock_pg.get_name.return_value = "OS_SG_%s_%s" % (sg_id, sg_name) - return mock_pg - - def _create_mock_rule(self, rule_id): - mock_rule = mock.Mock() - mock_rule.get_properties.return_value = {"os_sg_rule_id": rule_id} - return mock_rule - - -class MidonetChainManagerTestCase(MidonetLibTestCase): - - def setUp(self): - super(MidonetChainManagerTestCase, self).setUp() - self.mgr = midonet_lib.ChainManager(self.mock_api) + self.mock_api_cfg = mock_lib.MidoClientMockConfig(self.mock_api) + self.mock_api_cfg.setup() + self.client = midonet_lib.MidoClient(self.mock_api) def test_create_for_sg(self): - tenant_id = 'test_tenant' - sg_id = str(uuid.uuid4()) - sg_name = 'test_sg_name' - calls = [mock.call.add_chain().tenant_id(tenant_id)] + sg_id = uuidutils.generate_uuid() + sg_name = 'test-sg' + calls = [mock.call.add_chain().tenant_id(self._tenant_id), + mock.call.add_port_group().tenant_id(self._tenant_id)] - self.mgr.create_for_sg(tenant_id, sg_id, sg_name) + self.client.create_for_sg(self._tenant_id, sg_id, sg_name) - self.mock_api.assert_has_calls(calls) + self.mock_api.assert_has_calls(calls, any_order=True) - def test_delete_for_sg(self): - tenant_id = 'test_tenant' - sg_id = str(uuid.uuid4()) - sg_name = 'test_sg_name' - in_chain, out_chain = self._create_mock_chains(sg_id, sg_name) + def test_create_for_sg_rule(self): + sg_id = uuidutils.generate_uuid() + sg_name = 'test-sg' + in_chain_id = uuidutils.generate_uuid() + out_chain_id = uuidutils.generate_uuid() + self.mock_api_cfg.chains_in = [ + _create_test_sg_in_chain(sg_id, sg_name, in_chain_id, + self._tenant_id), + _create_test_sg_out_chain(sg_id, sg_name, out_chain_id, + self._tenant_id)] - # Mock get_chains returned values - self.mock_api.get_chains.return_value = [in_chain, out_chain] + sg_rule_id = uuidutils.generate_uuid() + sg_rule = _create_test_sg_rule(self._tenant_id, sg_id, sg_rule_id) - self.mgr.delete_for_sg(tenant_id, sg_id, sg_name) + props = {"os_sg_rule_id": sg_rule_id} + calls = [mock.call.add_rule().port_group(None).type( + 'accept').nw_proto(6).nw_src_address( + '192.168.1.0').nw_src_length(24).tp_src_start( + None).tp_src_end(None).tp_dst_start(1).tp_dst_end( + 65535).properties(props).create()] - self.mock_api.assert_has_calls(mock.call.get_chains( - {"tenant_id": tenant_id})) - in_chain.delete.assert_called_once_with() - out_chain.delete.assert_called_once_with() + self.client.create_for_sg_rule(sg_rule) - def test_get_router_chains(self): - tenant_id = 'test_tenant' - router_id = str(uuid.uuid4()) - in_chain, out_chain = self._create_mock_router_chains(router_id) - - # Mock get_chains returned values - self.mock_api.get_chains.return_value = [in_chain, out_chain] - - chains = self.mgr.get_router_chains(tenant_id, router_id) - - self.mock_api.assert_has_calls(mock.call.get_chains( - {"tenant_id": tenant_id})) - self.assertEqual(len(chains), 2) - self.assertEqual(chains['in'], in_chain) - self.assertEqual(chains['out'], out_chain) + # Egress chain rule added + self.mock_api_cfg.chains_out[0].assert_has_calls(calls) def test_create_router_chains(self): - tenant_id = 'test_tenant' - router_id = str(uuid.uuid4()) - calls = [mock.call.add_chain().tenant_id(tenant_id)] + router = mock_lib.get_router_mock(tenant_id=self._tenant_id) + api_calls = [mock.call.add_chain().tenant_id(self._tenant_id)] + router_calls = [ + mock.call.inbound_filter_id().outbound_filter_id().update()] - self.mgr.create_router_chains(tenant_id, router_id) + self.client.create_router_chains(router) + + self.mock_api.assert_has_calls(api_calls) + router.assert_has_calls(router_calls) + + def test_delete_for_sg(self): + sg_id = uuidutils.generate_uuid() + sg_name = 'test-sg' + in_chain_id = uuidutils.generate_uuid() + out_chain_id = uuidutils.generate_uuid() + pg_id = uuidutils.generate_uuid() + self.mock_api_cfg.chains_in = [ + _create_test_sg_in_chain(sg_id, sg_name, in_chain_id, + self._tenant_id), + _create_test_sg_out_chain(sg_id, sg_name, out_chain_id, + self._tenant_id)] + self.mock_api_cfg.port_groups_in = [ + _create_test_port_group(sg_id, sg_name, pg_id, self._tenant_id)] + + calls = [mock.call.get_chains({"tenant_id": self._tenant_id}), + mock.call.delete_chain(in_chain_id), + mock.call.delete_chain(out_chain_id), + mock.call.get_port_groups({"tenant_id": self._tenant_id}), + mock.call.delete_port_group(pg_id)] + + self.client.delete_for_sg(self._tenant_id, sg_id, sg_name) self.mock_api.assert_has_calls(calls) - def test_get_sg_chains(self): - tenant_id = 'test_tenant' - sg_id = str(uuid.uuid4()) - in_chain, out_chain = self._create_mock_chains(sg_id, 'foo') + def test_delete_for_sg_rule(self): + sg_id = uuidutils.generate_uuid() + sg_name = 'test-sg' + in_chain_id = uuidutils.generate_uuid() + out_chain_id = uuidutils.generate_uuid() + self.mock_api_cfg.chains_in = [ + _create_test_sg_in_chain(sg_id, sg_name, in_chain_id, + self._tenant_id), + _create_test_sg_out_chain(sg_id, sg_name, out_chain_id, + self._tenant_id)] - # Mock get_chains returned values - self.mock_api.get_chains.return_value = [in_chain, out_chain] + rule_id = uuidutils.generate_uuid() + sg_rule_id = uuidutils.generate_uuid() + rule = _create_test_sg_chain_rule(rule_id, in_chain_id, sg_rule_id) + self.mock_api_cfg.chains_in[0]['rules'] = [rule] + sg_rule = _create_test_sg_rule(self._tenant_id, sg_id, sg_rule_id) - chains = self.mgr.get_sg_chains(tenant_id, sg_id) + self.client.delete_for_sg_rule(sg_rule) - self.mock_api.assert_has_calls(mock.call.get_chains( - {"tenant_id": tenant_id})) - self.assertEqual(len(chains), 2) - self.assertEqual(chains['in'], in_chain) - self.assertEqual(chains['out'], out_chain) + self.mock_api.delete_rule.assert_called_once_with(rule_id) + def test_get_bridge(self): + bridge_id = uuidutils.generate_uuid() -class MidonetPortGroupManagerTestCase(MidonetLibTestCase): + bridge = self.client.get_bridge(bridge_id) - def setUp(self): - super(MidonetPortGroupManagerTestCase, self).setUp() - self.mgr = midonet_lib.PortGroupManager(self.mock_api) + self.assertIsNotNone(bridge) + self.assertEqual(bridge.get_id(), bridge_id) - def test_create(self): - tenant_id = 'test_tenant' - sg_id = str(uuid.uuid4()) - sg_name = 'test_sg' - pg_mock = self._create_mock_port_group(sg_id, sg_name) - rv = self.mock_api.add_port_group.return_value.tenant_id.return_value - rv.name.return_value = pg_mock + def test_get_bridge_error(self): + self.mock_api.get_bridge.side_effect = w_exc.HTTPInternalServerError() + self.assertRaises(midonet_lib.MidonetApiException, + self.client.get_bridge, uuidutils.generate_uuid()) - self.mgr.create(tenant_id, sg_id, sg_name) + def test_get_bridge_not_found(self): + self.mock_api.get_bridge.side_effect = w_exc.HTTPNotFound() + self.assertRaises(midonet_lib.MidonetResourceNotFound, + self.client.get_bridge, uuidutils.generate_uuid()) - pg_mock.create.assert_called_once_with() + def test_get_port_groups_for_sg(self): + sg_id = uuidutils.generate_uuid() + pg_id = uuidutils.generate_uuid() + self.mock_api_cfg.port_groups_in = [ + _create_test_port_group(sg_id, 'test-sg', pg_id, self._tenant_id)] - def test_delete(self): - tenant_id = 'test_tenant' - sg_id = str(uuid.uuid4()) - sg_name = 'test_sg' - pg_mock1 = self._create_mock_port_group(sg_id, sg_name) - pg_mock2 = self._create_mock_port_group(sg_id, sg_name) - self.mock_api.get_port_groups.return_value = [pg_mock1, pg_mock2] + pg = self.client.get_port_groups_for_sg(self._tenant_id, sg_id) - self.mgr.delete(tenant_id, sg_id, sg_name) - - self.mock_api.assert_has_calls(mock.call.get_port_groups( - {"tenant_id": tenant_id})) - pg_mock1.delete.assert_called_once_with() - pg_mock2.delete.assert_called_once_with() - - def test_get_for_sg(self): - tenant_id = 'test_tenant' - sg_id = str(uuid.uuid4()) - pg_mock = self._create_mock_port_group(sg_id, 'foo') - self.mock_api.get_port_groups.return_value = [pg_mock] - - pg = self.mgr.get_for_sg(tenant_id, sg_id) - - self.assertEqual(pg, pg_mock) - - -class MidonetRuleManagerTestCase(MidonetLibTestCase): - - def setUp(self): - super(MidonetRuleManagerTestCase, self).setUp() - self.mgr = midonet_lib.RuleManager(self.mock_api) - self.mgr.chain_manager = mock.Mock() - self.mgr.pg_manager = mock.Mock() + self.assertIsNotNone(pg) + self.assertEqual(pg.get_id(), pg_id) def _create_test_rule(self, tenant_id, sg_id, rule_id, direction="egress", protocol="tcp", port_min=1, port_max=65535, @@ -198,41 +226,53 @@ class MidonetRuleManagerTestCase(MidonetLibTestCase): "port_range_min": port_min, "port_range_max": port_max, "ethertype": ethertype, "id": rule_id, "external_id": None} - def test_create_for_sg_rule(self): - tenant_id = 'test_tenant' - sg_id = str(uuid.uuid4()) - rule_id = str(uuid.uuid4()) - in_chain, out_chain = self._create_mock_chains(sg_id, 'foo') - self.mgr.chain_manager.get_sg_chains.return_value = {"in": in_chain, - "out": out_chain} - props = {"os_sg_rule_id": rule_id} - rule = self._create_test_rule(tenant_id, sg_id, rule_id) - calls = [mock.call.add_rule().port_group(None).type( - 'accept').nw_proto(6).nw_src_address( - '192.168.1.0').nw_src_length(24).tp_src_start( - None).tp_src_end(None).tp_dst_start(1).tp_dst_end( - 65535).properties(props).create()] + def test_get_router_error(self): + self.mock_api.get_router.side_effect = w_exc.HTTPInternalServerError() + self.assertRaises(midonet_lib.MidonetApiException, + self.client.get_router, uuidutils.generate_uuid()) - self.mgr.create_for_sg_rule(rule) + def test_get_router_not_found(self): + self.mock_api.get_router.side_effect = w_exc.HTTPNotFound() + self.assertRaises(midonet_lib.MidonetResourceNotFound, + self.client.get_router, uuidutils.generate_uuid()) - in_chain.assert_has_calls(calls) + def test_get_router_chains(self): + router_id = uuidutils.generate_uuid() + in_chain_id = uuidutils.generate_uuid() + out_chain_id = uuidutils.generate_uuid() + self.mock_api_cfg.chains_in = [ + _create_test_router_in_chain(router_id, in_chain_id, + self._tenant_id), + _create_test_router_out_chain(router_id, out_chain_id, + self._tenant_id)] - def test_delete_for_sg_rule(self): - tenant_id = 'test_tenant' - sg_id = str(uuid.uuid4()) - rule_id = str(uuid.uuid4()) - in_chain, out_chain = self._create_mock_chains(sg_id, 'foo') - self.mgr.chain_manager.get_sg_chains.return_value = {"in": in_chain, - "out": out_chain} + chains = self.client.get_router_chains(self._tenant_id, router_id) - # Mock the rules returned for each chain - mock_rule_in = self._create_mock_rule(rule_id) - mock_rule_out = self._create_mock_rule(rule_id) - in_chain.get_rules.return_value = [mock_rule_in] - out_chain.get_rules.return_value = [mock_rule_out] + self.mock_api.assert_has_calls(mock.call.get_chains( + {"tenant_id": self._tenant_id})) + self.assertEqual(len(chains), 2) + self.assertIn('in', chains) + self.assertIn('out', chains) + self.assertEqual(chains['in'].get_id(), in_chain_id) + self.assertEqual(chains['out'].get_id(), out_chain_id) - rule = self._create_test_rule(tenant_id, sg_id, rule_id) - self.mgr.delete_for_sg_rule(rule) + def test_get_sg_chains(self): + sg_id = uuidutils.generate_uuid() + sg_name = 'test-sg' + in_chain_id = uuidutils.generate_uuid() + out_chain_id = uuidutils.generate_uuid() + self.mock_api_cfg.chains_in = [ + _create_test_sg_in_chain(sg_id, sg_name, in_chain_id, + self._tenant_id), + _create_test_sg_out_chain(sg_id, sg_name, out_chain_id, + self._tenant_id)] - mock_rule_in.delete.assert_called_once_with() - mock_rule_out.delete.assert_called_once_with() + chains = self.client.get_sg_chains(self._tenant_id, sg_id) + + self.mock_api.assert_has_calls(mock.call.get_chains( + {"tenant_id": self._tenant_id})) + self.assertEqual(len(chains), 2) + self.assertIn('in', chains) + self.assertIn('out', chains) + self.assertEqual(chains['in'].get_id(), in_chain_id) + self.assertEqual(chains['out'].get_id(), out_chain_id) diff --git a/quantum/tests/unit/midonet/test_midonet_plugin.py b/quantum/tests/unit/midonet/test_midonet_plugin.py index 424beadb13e..b33782b145d 100644 --- a/quantum/tests/unit/midonet/test_midonet_plugin.py +++ b/quantum/tests/unit/midonet/test_midonet_plugin.py @@ -20,16 +20,18 @@ # @author: Ryu Ishimoto, Midokura Japan KK # @author: Tomoe Sugihara, Midokura Japan KK -import sys -import uuid - import mock +import os +import sys +import quantum.common.test_lib as test_lib +import quantum.tests.unit.midonet.mock_lib as mock_lib import quantum.tests.unit.test_db_plugin as test_plugin MIDOKURA_PKG_PATH = "quantum.plugins.midonet.plugin" + # Need to mock the midonetclient module since the plugin will try to load it. sys.modules["midonetclient"] = mock.Mock() @@ -39,730 +41,83 @@ class MidonetPluginV2TestCase(test_plugin.QuantumDbPluginV2TestCase): _plugin_name = ('%s.MidonetPluginV2' % MIDOKURA_PKG_PATH) def setUp(self): - self.mock_api = mock.patch('midonetclient.api.MidonetApi') + self.mock_api = mock.patch( + 'quantum.plugins.midonet.midonet_lib.MidoClient') + etc_path = os.path.join(os.path.dirname(__file__), 'etc') + test_lib.test_config['config_files'] = [os.path.join( + etc_path, 'midonet.ini.test')] + self.instance = self.mock_api.start() + mock_cfg = mock_lib.MidonetLibMockConfig(self.instance.return_value) + mock_cfg.setup() super(MidonetPluginV2TestCase, self).setUp(self._plugin_name) def tearDown(self): super(MidonetPluginV2TestCase, self).tearDown() self.mock_api.stop() - def _setup_bridge_mock(self, bridge_id=str(uuid.uuid4()), name='net'): - # Set up mocks needed for the parent network() method - bridge = mock.Mock() - bridge.get_id.return_value = bridge_id - bridge.get_name.return_value = name - - self.instance.return_value.add_bridge.return_value.name.return_value\ - .tenant_id.return_value.create.return_value = bridge - self.instance.return_value.get_bridges.return_value = [bridge] - self.instance.return_value.get_bridge.return_value = bridge - return bridge - - def _setup_subnet_mocks(self, subnet_id=str(uuid.uuid4()), - subnet_prefix='10.0.0.0', subnet_len=int(24)): - # Set up mocks needed for the parent subnet() method - bridge = self._setup_bridge_mock() - subnet = mock.Mock() - subnet.get_subnet_prefix.return_value = subnet_prefix - subnet.get_subnet_length.return_value = subnet_len - subnet.get_id.return_value = subnet_prefix + '/' + str(subnet_len) - bridge.add_dhcp_subnet.return_value.default_gateway\ - .return_value.subnet_prefix.return_value.subnet_length\ - .return_value.create.return_value = subnet - bridge.get_dhcp_subnets.return_value = [subnet] - return (bridge, subnet) - - def _setup_port_mocks(self, port_id=str(uuid.uuid4())): - # Set up mocks needed for the parent port() method - bridge, subnet = self._setup_subnet_mocks() - port = mock.Mock() - port.get_id.return_value = port_id - self.instance.return_value.create_port.return_value = port - self.instance.return_value.get_port.return_value = port - bridge.add_exterior_port.return_value.create.return_value = ( - port - ) - - dhcp_host = mock.Mock() - rv1 = subnet.add_dhcp_host.return_value.ip_addr.return_value - rv1.mac_addr.return_value.create.return_value = dhcp_host - - subnet.get_dhcp_hosts.return_value = [dhcp_host] - return (bridge, subnet, port, dhcp_host) - class TestMidonetNetworksV2(test_plugin.TestNetworksV2, MidonetPluginV2TestCase): - - def test_create_network(self): - self._setup_bridge_mock() - super(TestMidonetNetworksV2, self).test_create_network() - - def test_create_public_network(self): - self._setup_bridge_mock() - super(TestMidonetNetworksV2, self).test_create_public_network() - - def test_create_public_network_no_admin_tenant(self): - self._setup_bridge_mock() - super(TestMidonetNetworksV2, - self).test_create_public_network_no_admin_tenant() - - def test_update_network(self): - self._setup_bridge_mock() - super(TestMidonetNetworksV2, self).test_update_network() - - def test_list_networks(self): - self._setup_bridge_mock() - with self.network(name='net1') as net1: - req = self.new_list_request('networks') - res = self.deserialize('json', req.get_response(self.api)) - self.assertEqual(res['networks'][0]['name'], - net1['network']['name']) - - def test_show_network(self): - self._setup_bridge_mock() - super(TestMidonetNetworksV2, self).test_show_network() - - def test_update_shared_network_noadmin_returns_403(self): - self._setup_bridge_mock() - super(TestMidonetNetworksV2, - self).test_update_shared_network_noadmin_returns_403() - - def test_update_network_set_shared(self): - pass - - def test_update_network_with_subnet_set_shared(self): - pass - - def test_update_network_set_not_shared_single_tenant(self): - pass - - def test_update_network_set_not_shared_other_tenant_returns_409(self): - pass - - def test_update_network_set_not_shared_multi_tenants_returns_409(self): - pass - - def test_update_network_set_not_shared_multi_tenants2_returns_409(self): - pass - - def test_create_networks_bulk_native(self): - pass - - def test_create_networks_bulk_native_quotas(self): - pass - - def test_create_networks_bulk_tenants_and_quotas(self): - pass - - def test_create_networks_bulk_tenants_and_quotas_fail(self): - pass - - def test_create_networks_bulk_emulated(self): - pass - - def test_create_networks_bulk_wrong_input(self): - pass - - def test_create_networks_bulk_emulated_plugin_failure(self): - pass - - def test_create_networks_bulk_native_plugin_failure(self): - pass - - def test_list_networks_with_parameters(self): - pass - - def test_list_networks_with_fields(self): - pass - - def test_list_networks_with_parameters_invalid_values(self): - pass - - def test_list_shared_networks_with_non_admin_user(self): - pass - - def test_show_network_with_subnet(self): - pass - - def test_invalid_admin_status(self): - pass - - def test_list_networks_with_pagination_emulated(self): - pass - - def test_list_networks_with_pagination_reverse_emulated(self): - pass - - def test_list_networks_with_sort_emulated(self): - pass - - def test_list_networks_without_pk_in_fields_pagination_emulated(self): - pass + pass class TestMidonetSubnetsV2(test_plugin.TestSubnetsV2, MidonetPluginV2TestCase): - def test_create_subnet(self): - self._setup_subnet_mocks() - super(TestMidonetSubnetsV2, self).test_create_subnet() - - def test_create_two_subnets(self): - pass - - def test_create_two_subnets_same_cidr_returns_400(self): - pass - - def test_create_subnet_bad_V4_cidr(self): - self._setup_subnet_mocks() - super(TestMidonetSubnetsV2, self).test_create_subnet_bad_V4_cidr() - - def test_create_subnet_bad_V6_cidr(self): - self._setup_subnet_mocks() - super(TestMidonetSubnetsV2, self).test_create_subnet_bad_V4_cidr() - - def test_create_2_subnets_overlapping_cidr_allowed_returns_200(self): - pass - - def test_create_2_subnets_overlapping_cidr_not_allowed_returns_400(self): - pass - - def test_create_subnets_bulk_native(self): - pass - - def test_create_subnets_bulk_emulated(self): - pass - - def test_create_subnets_bulk_emulated_plugin_failure(self): - pass - - def test_create_subnets_bulk_native_plugin_failure(self): - pass - - def test_delete_subnet(self): - _bridge, subnet = self._setup_subnet_mocks() - super(TestMidonetSubnetsV2, self).test_delete_subnet() - subnet.delete.assert_called_once_with() - - def test_delete_subnet_port_exists_owned_by_network(self): - _bridge, subnet = self._setup_subnet_mocks() - super(TestMidonetSubnetsV2, - self).test_delete_subnet_port_exists_owned_by_network() - - def test_delete_subnet_port_exists_owned_by_other(self): - pass - - def test_delete_network(self): - bridge, _subnet = self._setup_subnet_mocks() - super(TestMidonetSubnetsV2, self).test_delete_network() - bridge.delete.assert_called_once_with() - - def test_create_subnet_bad_tenant(self): - self._setup_subnet_mocks() - super(TestMidonetSubnetsV2, self).test_create_subnet_bad_tenant() - - def test_create_subnet_bad_ip_version(self): - self._setup_subnet_mocks() - super(TestMidonetSubnetsV2, self).test_create_subnet_bad_ip_version() - - def test_create_subnet_bad_ip_version_null(self): - self._setup_subnet_mocks() - super(TestMidonetSubnetsV2, - self).test_create_subnet_bad_ip_version_null() - - def test_create_subnet_bad_uuid(self): - self._setup_subnet_mocks() - super(TestMidonetSubnetsV2, self).test_create_subnet_bad_uuid() - - def test_create_subnet_bad_boolean(self): - self._setup_subnet_mocks() - super(TestMidonetSubnetsV2, self).test_create_subnet_bad_boolean() - - def test_create_subnet_bad_pools(self): - self._setup_subnet_mocks() - super(TestMidonetSubnetsV2, self).test_create_subnet_bad_pools() - - def test_create_subnet_bad_nameserver(self): - self._setup_subnet_mocks() - super(TestMidonetSubnetsV2, self).test_create_subnet_bad_nameserver() - - def test_create_subnet_bad_hostroutes(self): - self._setup_subnet_mocks() - super(TestMidonetSubnetsV2, self).test_create_subnet_bad_hostroutes() - - def test_create_subnet_defaults(self): - self._setup_subnet_mocks() - super(TestMidonetSubnetsV2, self).test_create_subnet_defaults() - - def test_create_subnet_gw_values(self): - self._setup_subnet_mocks() - super(TestMidonetSubnetsV2, self).test_create_subnet_gw_values() - - def test_create_subnet_gw_outside_cidr_force_on_returns_400(self): - self._setup_subnet_mocks() - super(TestMidonetSubnetsV2, - self).test_create_subnet_gw_outside_cidr_force_on_returns_400() - - def test_create_subnet_gw_of_network_force_on_returns_400(self): - self._setup_subnet_mocks() - super(TestMidonetSubnetsV2, - self).test_create_subnet_gw_of_network_force_on_returns_400() - - def test_create_subnet_gw_bcast_force_on_returns_400(self): - self._setup_subnet_mocks() - super(TestMidonetSubnetsV2, - self).test_create_subnet_gw_bcast_force_on_returns_400() - - def test_create_subnet_with_allocation_pool(self): - self._setup_subnet_mocks() - super(TestMidonetSubnetsV2, - self).test_create_subnet_with_allocation_pool() - - def test_create_subnet_with_none_gateway(self): - self._setup_subnet_mocks() - super(TestMidonetSubnetsV2, - self).test_create_subnet_with_none_gateway() - - def test_create_subnet_with_none_gateway_fully_allocated(self): - self._setup_subnet_mocks() - super(TestMidonetSubnetsV2, - self).test_create_subnet_with_none_gateway_fully_allocated() - - def test_subnet_with_allocation_range(self): - pass - - def test_create_subnet_with_none_gateway_allocation_pool(self): - self._setup_subnet_mocks() - super(TestMidonetSubnetsV2, - self).test_create_subnet_with_none_gateway_allocation_pool() - - def test_create_subnet_with_v6_allocation_pool(self): - pass - - def test_create_subnet_with_large_allocation_pool(self): - pass - - def test_create_subnet_multiple_allocation_pools(self): - pass - - def test_create_subnet_with_dhcp_disabled(self): - pass - - def test_create_subnet_default_gw_conflict_allocation_pool_returns_409( - self): - pass - - def test_create_subnet_gateway_in_allocation_pool_returns_409(self): - self._setup_subnet_mocks() - super(TestMidonetSubnetsV2, self)\ - .test_create_subnet_gateway_in_allocation_pool_returns_409() - - def test_create_subnet_overlapping_allocation_pools_returns_409(self): - self._setup_subnet_mocks() - super(TestMidonetSubnetsV2, self)\ - .test_create_subnet_overlapping_allocation_pools_returns_409() - - def test_create_subnet_invalid_allocation_pool_returns_400(self): - self._setup_subnet_mocks() - super(TestMidonetSubnetsV2, - self).test_create_subnet_invalid_allocation_pool_returns_400() - - def test_create_subnet_out_of_range_allocation_pool_returns_400(self): - self._setup_subnet_mocks() - super(TestMidonetSubnetsV2, self)\ - .test_create_subnet_out_of_range_allocation_pool_returns_400() - - def test_create_subnet_shared_returns_400(self): - self._setup_subnet_mocks() - super(TestMidonetSubnetsV2, - self).test_create_subnet_shared_returns_400() - + # IPv6 is not supported by MidoNet yet. Ignore tests that attempt to + # create IPv6 subnet. def test_create_subnet_inconsistent_ipv6_cidrv4(self): pass - def test_create_subnet_inconsistent_ipv4_cidrv6(self): - pass - - def test_create_subnet_inconsistent_ipv4_gatewayv6(self): - pass - - def test_create_subnet_inconsistent_ipv6_gatewayv4(self): - pass - def test_create_subnet_inconsistent_ipv6_dns_v4(self): pass - def test_create_subnet_inconsistent_ipv4_hostroute_dst_v6(self): - pass - - def test_create_subnet_inconsistent_ipv4_hostroute_np_v6(self): - pass - - def test_update_subnet(self): - _bridge, _subnet = self._setup_subnet_mocks() - super(TestMidonetSubnetsV2, self).test_update_subnet() - - def test_update_subnet_shared_returns_400(self): - self._setup_subnet_mocks() - super(TestMidonetSubnetsV2, - self).test_update_subnet_shared_returns_400() - - def test_update_subnet_gw_outside_cidr_force_on_returns_400(self): - pass - - def test_update_subnet_inconsistent_ipv4_gatewayv6(self): + def test_create_subnet_with_v6_allocation_pool(self): pass def test_update_subnet_inconsistent_ipv6_gatewayv4(self): pass - def test_update_subnet_inconsistent_ipv4_dns_v6(self): - pass - def test_update_subnet_inconsistent_ipv6_hostroute_dst_v4(self): pass def test_update_subnet_inconsistent_ipv6_hostroute_np_v4(self): pass - def test_show_subnet(self): - _bridge, _subnet = self._setup_subnet_mocks() - super(TestMidonetSubnetsV2, self).test_show_subnet() - - def test_list_subnets(self): + def test_create_subnet_inconsistent_ipv6_gatewayv4(self): pass - def test_list_subnets_shared(self): + # Multiple subnets in a network is not supported by MidoNet yet. Ignore + # tests that attempt to create them. + + def test_create_subnets_bulk_emulated(self): + pass + + def test_create_two_subnets(self): + pass + + def test_list_subnets(self): pass def test_list_subnets_with_parameter(self): pass - def test_invalid_ip_version(self): - _bridge, _subnet = self._setup_subnet_mocks() - super(TestMidonetSubnetsV2, self).test_invalid_ip_version() - - def test_invalid_subnet(self): - _bridge, _subnet = self._setup_subnet_mocks() - super(TestMidonetSubnetsV2, self).test_invalid_subnet() - - def test_invalid_ip_address(self): - _bridge, _subnet = self._setup_subnet_mocks() - super(TestMidonetSubnetsV2, self).test_invalid_ip_address() - - def test_invalid_uuid(self): - _bridge, _subnet = self._setup_subnet_mocks() - super(TestMidonetSubnetsV2, self).test_invalid_uuid() - - def test_create_subnet_with_one_dns(self): - _bridge, _subnet = self._setup_subnet_mocks() - super(TestMidonetSubnetsV2, self).test_create_subnet_with_one_dns() - - def test_create_subnet_with_two_dns(self): - _bridge, _subnet = self._setup_subnet_mocks() - super(TestMidonetSubnetsV2, self).test_create_subnet_with_two_dns() - - def test_create_subnet_with_too_many_dns(self): - _bridge, _subnet = self._setup_subnet_mocks() - super(TestMidonetSubnetsV2, - self).test_create_subnet_with_too_many_dns() - - def test_create_subnet_with_one_host_route(self): - _bridge, _subnet = self._setup_subnet_mocks() - super(TestMidonetSubnetsV2, - self).test_create_subnet_with_one_host_route() - - def test_create_subnet_with_two_host_routes(self): - _bridge, _subnet = self._setup_subnet_mocks() - super(TestMidonetSubnetsV2, - self).test_create_subnet_with_two_host_routes() - - def test_create_subnet_with_too_many_routes(self): - _bridge, _subnet = self._setup_subnet_mocks() - super(TestMidonetSubnetsV2, - self).test_create_subnet_with_too_many_routes() - - def test_update_subnet_dns(self): - _bridge, _subnet = self._setup_subnet_mocks() - super(TestMidonetSubnetsV2, self).test_update_subnet_dns() - - def test_update_subnet_dns_to_None(self): - _bridge, _subnet = self._setup_subnet_mocks() - super(TestMidonetSubnetsV2, self).test_update_subnet_dns_to_None() - - def test_update_subnet_dns_with_too_many_entries(self): - _bridge, _subnet = self._setup_subnet_mocks() - super(TestMidonetSubnetsV2, - self).test_update_subnet_dns_with_too_many_entries() - - def test_update_subnet_route(self): - _bridge, _subnet = self._setup_subnet_mocks() - super(TestMidonetSubnetsV2, self).test_update_subnet_route() - - def test_update_subnet_route_to_None(self): - _bridge, _subnet = self._setup_subnet_mocks() - super(TestMidonetSubnetsV2, - self).test_update_subnet_route_to_None() - - def test_update_subnet_route_with_too_many_entries(self): - _bridge, _subnet = self._setup_subnet_mocks() - super(TestMidonetSubnetsV2, - self).test_update_subnet_route_with_too_many_entries() - - def test_delete_subnet_with_dns(self): - _bridge, subnet = self._setup_subnet_mocks() - super(TestMidonetSubnetsV2, self).test_delete_subnet_with_dns() - subnet.delete.assert_called_once_with() - - def test_delete_subnet_with_route(self): - _bridge, subnet = self._setup_subnet_mocks() - super(TestMidonetSubnetsV2, self).test_delete_subnet_with_route() - subnet.delete.assert_called_once_with() - - def test_delete_subnet_with_dns_and_route(self): - _bridge, subnet = self._setup_subnet_mocks() - super(TestMidonetSubnetsV2, - self).test_delete_subnet_with_dns_and_route() - subnet.delete.assert_called_once_with() - - def test_update_subnet_gateway_in_allocation_pool_returns_409(self): - self._setup_port_mocks() - super(TestMidonetSubnetsV2, self)\ - .test_update_subnet_gateway_in_allocation_pool_returns_409() - - def test_list_subnets_with_pagination_emulated(self): - pass - - def test_list_subnets_with_pagination_reverse_emulated(self): - pass - - def test_list_subnets_with_sort_emulated(self): + def test_create_two_subnets_same_cidr_returns_400(self): pass class TestMidonetPortsV2(test_plugin.TestPortsV2, MidonetPluginV2TestCase): - def test_create_port_json(self): - self._setup_port_mocks() - super(TestMidonetPortsV2, self).test_create_port_json() - - def test_create_port_bad_tenant(self): - self._setup_port_mocks() - super(TestMidonetPortsV2, self).test_create_port_bad_tenant() - - def test_create_port_public_network(self): - self._setup_port_mocks() - super(TestMidonetPortsV2, self).test_create_port_public_network() - - def test_create_port_public_network_with_ip(self): - self._setup_port_mocks() - super(TestMidonetPortsV2, - self).test_create_port_public_network_with_ip() - - def test_create_ports_bulk_native(self): - pass - - def test_create_ports_bulk_emulated(self): - pass - - def test_create_ports_bulk_wrong_input(self): - pass - - def test_create_ports_bulk_emulated_plugin_failure(self): - pass - - def test_create_ports_bulk_native_plugin_failure(self): - pass - - def test_list_ports(self): - pass - - def test_list_ports_filtered_by_fixed_ip(self): - pass - - def test_list_ports_public_network(self): - pass - - def test_show_port(self): - self._setup_port_mocks() - super(TestMidonetPortsV2, self).test_show_port() - - def test_delete_port(self): - _bridge, _subnet, port, _dhcp = self._setup_port_mocks() - super(TestMidonetPortsV2, self).test_delete_port() - port.delete.assert_called_once_with() - - def test_delete_port_public_network(self): - _bridge, _subnet, port, _dhcp = self._setup_port_mocks() - super(TestMidonetPortsV2, self).test_delete_port_public_network() - port.delete.assert_called_once_with() - - def test_update_port(self): - self._setup_port_mocks() - super(TestMidonetPortsV2, self).test_update_port() - - def test_update_device_id_null(self): - self._setup_port_mocks() - super(TestMidonetPortsV2, self).test_update_device_id_null() - - def test_delete_network_if_port_exists(self): - self._setup_port_mocks() - super(TestMidonetPortsV2, self).test_delete_network_if_port_exists() - - def test_delete_network_port_exists_owned_by_network(self): - self._setup_port_mocks() - super(TestMidonetPortsV2, - self).test_delete_network_port_exists_owned_by_network() - - def test_update_port_delete_ip(self): - pass - - def test_no_more_port_exception(self): - pass - - def test_update_port_update_ip(self): - self._setup_port_mocks() - super(TestMidonetPortsV2, self).test_update_port_update_ip() - - def test_update_port_update_ip_address_only(self): - self._setup_port_mocks() - super(TestMidonetPortsV2, - self).test_update_port_update_ip_address_only() - - def test_update_port_update_ips(self): - self._setup_port_mocks() - super(TestMidonetPortsV2, self).test_update_port_update_ips() - - def test_update_port_add_additional_ip(self): - self._setup_port_mocks() - super(TestMidonetPortsV2, self).test_update_port_add_additional_ip() - - def test_requested_duplicate_mac(self): - self._setup_port_mocks() - super(TestMidonetPortsV2, self).test_requested_duplicate_mac() - - def test_mac_generation(self): - self._setup_port_mocks() - super(TestMidonetPortsV2, self).test_mac_generation() - - def test_mac_generation_4octet(self): - self._setup_port_mocks() - super(TestMidonetPortsV2, self).test_mac_generation_4octet() - - def test_bad_mac_format(self): - self._setup_port_mocks() - super(TestMidonetPortsV2, self).test_bad_mac_format() - - def test_mac_exhaustion(self): - self._setup_port_mocks() - super(TestMidonetPortsV2, self).test_mac_exhaustion() - - def test_requested_duplicate_ip(self): - self._setup_port_mocks() - super(TestMidonetPortsV2, self).test_requested_duplicate_ip() - - def test_requested_subnet_delete(self): - self._setup_port_mocks() - super(TestMidonetPortsV2, self).test_requested_subnet_delete() - - def test_requested_subnet_id(self): - pass - - def test_requested_subnet_id_not_on_network(self): - pass - - def test_overlapping_subnets(self): - pass + # IPv6 is not supported by MidoNet yet. Ignore tests that attempt to + # create IPv6 subnet. def test_requested_subnet_id_v4_and_v6(self): pass - def test_range_allocation(self): - pass + # Multiple subnets in a network is not supported by MidoNet yet. Ignore + # tests that attempt to create them. - def test_requested_invalid_fixed_ips(self): - pass - - def test_invalid_ip(self): - self._setup_port_mocks() - super(TestMidonetPortsV2, self).test_invalid_ip() - - def test_requested_split(self): - pass - - def test_duplicate_ips(self): - self._setup_port_mocks() - super(TestMidonetPortsV2, self).test_duplicate_ips() - - def test_fixed_ip_invalid_subnet_id(self): - self._setup_port_mocks() - super(TestMidonetPortsV2, self).test_fixed_ip_invalid_subnet_id() - - def test_fixed_ip_invalid_ip(self): - self._setup_port_mocks() - super(TestMidonetPortsV2, self).test_fixed_ip_invalid_ip() - - def test_requested_ips_only(self): - pass - - def test_recycling(self): - pass - - def test_invalid_admin_state(self): - self._setup_port_mocks() - super(TestMidonetPortsV2, self).test_invalid_admin_state() - - def test_invalid_mac_address(self): - self._setup_port_mocks() - super(TestMidonetPortsV2, self).test_invalid_mac_address() - - def test_default_allocation_expiration(self): - self._setup_port_mocks() - super(TestMidonetPortsV2, self).test_default_allocation_expiration() - - def test_update_fixed_ip_lease_expiration(self): - self._setup_port_mocks() - super(TestMidonetPortsV2, - self).test_update_fixed_ip_lease_expiration() - - def test_port_delete_holds_ip(self): - self._setup_port_mocks() - super(TestMidonetPortsV2, self).test_port_delete_holds_ip() - - def test_update_fixed_ip_lease_expiration_invalid_address(self): - self._setup_port_mocks() - super(TestMidonetPortsV2, - self).test_update_fixed_ip_lease_expiration_invalid_address() - - def test_hold_ip_address(self): - self._setup_port_mocks() - super(TestMidonetPortsV2, self).test_hold_ip_address() - - def test_recycle_held_ip_address(self): - self._setup_port_mocks() - super(TestMidonetPortsV2, self).test_recycle_held_ip_address() - - def test_recycle_expired_previously_run_within_context(self): - pass - - def test_update_port_not_admin(self): - self._setup_port_mocks() - super(TestMidonetPortsV2, self).test_update_port_not_admin() - - def test_list_ports_with_pagination_emulated(self): - pass - - def test_list_ports_with_pagination_reverse_emulated(self): - pass - - def test_list_ports_with_sort_emulated(self): - pass - - def test_max_fixed_ips_exceeded(self): - pass - - def test_update_max_fixed_ips_exceeded(self): - pass + def test_overlapping_subnets(self): + pass