from heat.engine import clients
from heat.engine import resource
+from heat.engine.resources.vpc import VPC
+from heat.common import exception
from heat.openstack.common import excutils
from heat.openstack.common import log as logging
class ElasticIp(resource.Resource):
properties_schema = {'Domain': {'Type': 'String',
- 'Implemented': False},
+ 'AllowedValues': ['vpc']},
'InstanceId': {'Type': 'String'}}
attributes_schema = {
"AllocationId": ("ID that AWS assigns to represent the allocation of"
self.ipaddress = None
def _ipaddress(self):
- if self.ipaddress is None:
- if self.resource_id is not None:
+ if self.ipaddress is None and self.resource_id is not None:
+ if self.properties['Domain'] and clients.neutronclient:
+ ne = clients.neutronclient.exceptions.NeutronClientException
+ try:
+ ips = self.neutron().show_floatingip(self.resource_id)
+ except ne as e:
+ if e.status_code == 404:
+ logger.warn("Floating IPs not found: %s" % str(e))
+ else:
+ self.ipaddress = ips['floatingip']['floating_ip_address']
+ else:
try:
ips = self.nova().floating_ips.get(self.resource_id)
except clients.novaclient.exceptions.NotFound as ex:
def handle_create(self):
"""Allocate a floating IP for the current tenant."""
- try:
- ips = self.nova().floating_ips.create()
- except clients.novaclient.exceptions.NotFound:
- with excutils.save_and_reraise_exception():
- msg = ("No default floating IP pool configured."
- "Set 'default_floating_pool' in nova.conf.")
- logger.error(msg)
-
- if ips:
+ ips = None
+ if self.properties['Domain'] and clients.neutronclient:
+ from heat.engine.resources.internet_gateway import InternetGateway
+
+ ext_net = InternetGateway.get_external_network_id(self.neutron())
+ props = {'floating_network_id': ext_net}
+ ips = self.neutron().create_floatingip({
+ 'floatingip': props})['floatingip']
+ self.ipaddress = ips['floating_ip_address']
+ self.resource_id_set(ips['id'])
logger.info('ElasticIp create %s' % str(ips))
- self.ipaddress = ips.ip
- self.resource_id_set(ips.id)
+ else:
+ if self.properties['Domain']:
+ raise exception.Error('Domain property can not be set on '
+ 'resource %s without Neutron available' %
+ self.name)
+ try:
+ ips = self.nova().floating_ips.create()
+ except clients.novaclient.exceptions.NotFound:
+ with excutils.save_and_reraise_exception():
+ msg = ("No default floating IP pool configured."
+ "Set 'default_floating_pool' in nova.conf.")
+ logger.error(msg)
+
+ if ips:
+ self.ipaddress = ips.ip
+ self.resource_id_set(ips.id)
+ logger.info('ElasticIp create %s' % str(ips))
if self.properties['InstanceId']:
server = self.nova().servers.get(self.properties['InstanceId'])
"""De-allocate a floating IP."""
if self.resource_id is not None:
- self.nova().floating_ips.delete(self.resource_id)
+ if self.properties['Domain'] and clients.neutronclient:
+ ne = clients.neutronclient.exceptions.NeutronClientException
+ try:
+ self.neutron().delete_floatingip(self.resource_id)
+ except ne as e:
+ if e.status_code != 404:
+ raise e
+ else:
+ self.nova().floating_ips.delete(self.resource_id)
def FnGetRefId(self):
return unicode(self._ipaddress())
properties_schema = {'InstanceId': {'Type': 'String',
'Required': False},
'EIP': {'Type': 'String'},
- 'AllocationId': {'Type': 'String',
- 'Implemented': False}}
+ 'AllocationId': {'Type': 'String'},
+ 'NetworkInterfaceId': {'Type': 'String'}}
def FnGetRefId(self):
- return unicode(self.properties.get('EIP', '0.0.0.0'))
+ return unicode(self.physical_resource_name())
def handle_create(self):
"""Add a floating IP address to a server."""
- logger.debug('ElasticIpAssociation %s.add_floating_ip(%s)' %
- (self.properties['InstanceId'],
- self.properties['EIP']))
-
- if self.properties['InstanceId']:
+ if self.properties['EIP'] is not None \
+ and self.properties['AllocationId'] is not None:
+ raise exception.ResourcePropertyConflict('EIP',
+ 'AllocationId')
+
+ if self.properties['EIP']:
+ if not self.properties['InstanceId']:
+ logger.warn('Skipping association, InstanceId not specified')
+ return
server = self.nova().servers.get(self.properties['InstanceId'])
server.add_floating_ip(self.properties['EIP'])
- self.resource_id_set(self.properties['EIP'])
+ self.resource_id_set(self.properties['EIP'])
+ logger.debug('ElasticIpAssociation %s.add_floating_ip(%s)' %
+ (self.properties['InstanceId'],
+ self.properties['EIP']))
+ elif self.properties['AllocationId']:
+ assert clients.neutronclient, "Neutron required for VPC operations"
+ port_id = None
+ port_rsrc = None
+ if self.properties['NetworkInterfaceId']:
+ port_id = self.properties['NetworkInterfaceId']
+ port_rsrc = self.neutron().list_ports(id=port_id)['ports'][0]
+ elif self.properties['InstanceId']:
+ instance_id = self.properties['InstanceId']
+ ports = self.neutron().list_ports(device_id=instance_id)
+ port_rsrc = ports['ports'][0]
+ port_id = port_rsrc['id']
+ else:
+ logger.warn('Skipping association, resource not specified')
+ return
+
+ float_id = self.properties['AllocationId']
+ self.resource_id_set(float_id)
+
+ # assuming only one fixed_ip
+ subnet_id = port_rsrc['fixed_ips'][0]['subnet_id']
+ subnets = self.neutron().list_subnets(id=subnet_id)
+ subnet_rsrc = subnets['subnets'][0]
+ netid = subnet_rsrc['network_id']
+
+ router_id = VPC.router_for_vpc(self.neutron(), netid)['id']
+ floatingip = self.neutron().show_floatingip(float_id)
+ floating_net_id = floatingip['floatingip']['floating_network_id']
+
+ self.neutron().add_gateway_router(
+ router_id, {'network_id': floating_net_id})
+
+ self.neutron().update_floatingip(
+ float_id, {'floatingip': {'port_id': port_id}})
def handle_delete(self):
- """Remove a floating IP address from a server."""
- if self.properties['InstanceId']:
+ """Remove a floating IP address from a server or port."""
+ if self.properties['EIP']:
try:
server = self.nova().servers.get(self.properties['InstanceId'])
if server:
server.remove_floating_ip(self.properties['EIP'])
except clients.novaclient.exceptions.NotFound as ex:
pass
+ elif self.properties['AllocationId']:
+ float_id = self.properties['AllocationId']
+ ne = clients.neutronclient.exceptions.NeutronClientException
+ try:
+ self.neutron().update_floatingip(
+ float_id, {'floatingip': {'port_id': None}})
+ except ne as e:
+ if e.status_code != 404:
+ raise e
def resource_mapping():
# License for the specific language governing permissions and limitations
# under the License.
+from testtools import skipIf
from heat.common import exception
from heat.common import template_format
from heat.engine import clients
from heat.engine import resource
from heat.engine import scheduler
+from heat.engine import parser
from heat.tests.common import HeatTestCase
from heat.tests.v1_1 import fakes
+from heat.tests import fakes as fakec
from heat.tests import utils
}
'''
+eip_template_ipassoc2 = '''
+{
+ "AWSTemplateFormatVersion" : "2010-09-09",
+ "Description" : "EIP Test",
+ "Parameters" : {},
+ "Resources" : {
+ "the_eip" : {
+ "Type" : "AWS::EC2::EIP",
+ "Properties" : {
+ "Domain": "vpc"
+ }
+ },
+ "IPAssoc" : {
+ "Type" : "AWS::EC2::EIPAssociation",
+ "Properties" : {
+ "AllocationId" : 'fc68ea2c-b60b-4b4f-bd82-94ec81110766',
+ "NetworkInterfaceId" : { "Ref" : "the_nic" }
+ }
+ },
+ "the_vpc" : {
+ "Type" : "AWS::EC2::VPC",
+ "Properties" : {
+ "CidrBlock" : "10.0.0.0/16"
+ }
+ },
+ "the_subnet" : {
+ "Type" : "AWS::EC2::Subnet",
+ "Properties" : {
+ "CidrBlock" : "10.0.0.0/24",
+ "VpcId" : { "Ref" : "the_vpc" }
+ }
+ },
+ "the_nic" : {
+ "Type" : "AWS::EC2::NetworkInterface",
+ "Properties" : {
+ "PrivateIpAddress": "10.0.0.100",
+ "SubnetId": { "Ref": "the_subnet" }
+ }
+ },
+ }
+}
+'''
+
+
+def force_networking(mode):
+ if mode == 'nova':
+ force_networking.client = clients.neutronclient
+ clients.neutronclient = None
+ if mode == 'neutron':
+ clients.neutronclient = force_networking.client
+force_networking.client = None
+
class EIPTest(HeatTestCase):
def setUp(self):
+ # force Nova, will test Neutron below
+ force_networking('nova')
super(EIPTest, self).setUp()
self.fc = fakes.FakeClient()
self.m.StubOutWithMock(eip.ElasticIp, 'nova')
self.m.StubOutWithMock(self.fc.servers, 'get')
utils.setup_dummy_db()
+ def tearDown(self):
+ super(EIPTest, self).tearDown()
+ force_networking('neutron')
+
def create_eip(self, t, stack, resource_name):
rsrc = eip.ElasticIp(resource_name,
t['Resources'][resource_name],
return rsrc
def test_eip(self):
-
eip.ElasticIp.nova().MultipleTimes().AndReturn(self.fc)
self.fc.servers.get('WebServer').AndReturn(self.fc.servers.list()[0])
self.fc.servers.get('WebServer')
try:
self.assertEqual('11.0.0.1', rsrc.FnGetRefId())
- rsrc.ipaddress = None
+ rsrc.refid = None
self.assertEqual('11.0.0.1', rsrc.FnGetRefId())
self.assertEqual('1', rsrc.FnGetAtt('AllocationId'))
self.m.VerifyAll()
+ def test_association_eip(self):
+ eip.ElasticIp.nova().AndReturn(self.fc)
+ eip.ElasticIp.nova().AndReturn(self.fc)
+
+ self.m.ReplayAll()
+
+ t = template_format.parse(eip_template_ipassoc)
+ stack = utils.parse_stack(t)
+
+ rsrc = self.create_eip(t, stack, 'IPAddress')
+ association = self.create_association(t, stack, 'IPAssoc')
+
+ # TODO(sbaker), figure out why this is an empty string
+ #self.assertEqual('', association.FnGetRefId())
+
+ association.delete()
+ rsrc.delete()
+
+ self.m.VerifyAll()
+
def test_eip_with_exception(self):
self.m.StubOutWithMock(self.fc.floating_ips, 'create')
eip.ElasticIp.nova().MultipleTimes().AndReturn(self.fc)
rsrc.handle_create)
self.m.VerifyAll()
- def test_association(self):
- eip.ElasticIp.nova().AndReturn(self.fc)
- eip.ElasticIpAssociation.nova().AndReturn(self.fc)
- self.fc.servers.get('WebServer').AndReturn(self.fc.servers.list()[0])
- eip.ElasticIpAssociation.nova().AndReturn(self.fc)
+
+class AllocTest(HeatTestCase):
+
+ @skipIf(clients.neutronclient is None, 'neutronclient unavailable')
+ def setUp(self):
+ super(AllocTest, self).setUp()
+
+ self.fc = fakes.FakeClient()
+ self.m.StubOutWithMock(eip.ElasticIp, 'nova')
+ self.m.StubOutWithMock(eip.ElasticIpAssociation, 'nova')
+ self.m.StubOutWithMock(self.fc.servers, 'get')
+
+ self.m.StubOutWithMock(parser.Stack, 'resource_by_refid')
+ self.m.StubOutWithMock(clients.neutronclient.Client,
+ 'create_floatingip')
+ self.m.StubOutWithMock(clients.neutronclient.Client,
+ 'show_floatingip')
+ self.m.StubOutWithMock(clients.neutronclient.Client,
+ 'update_floatingip')
+ self.m.StubOutWithMock(clients.neutronclient.Client,
+ 'delete_floatingip')
+ self.m.StubOutWithMock(clients.neutronclient.Client,
+ 'add_gateway_router')
+ self.m.StubOutWithMock(clients.neutronclient.Client, 'list_networks')
+ self.m.StubOutWithMock(clients.neutronclient.Client, 'list_ports')
+ self.m.StubOutWithMock(clients.neutronclient.Client, 'list_subnets')
+ self.m.StubOutWithMock(clients.neutronclient.Client, 'show_network')
+ self.m.StubOutWithMock(clients.neutronclient.Client, 'list_routers')
+ self.m.StubOutWithMock(clients.neutronclient.Client,
+ 'remove_gateway_router')
+ self.m.StubOutWithMock(clients.OpenStackClients, 'keystone')
+
+ utils.setup_dummy_db()
+
+ def mock_show_network(self):
+ vpc_name = utils.PhysName('test_stack', 'the_vpc')
+ clients.neutronclient.Client.show_network(
+ 'aaaa-netid'
+ ).AndReturn({"network": {
+ "status": "BUILD",
+ "subnets": [],
+ "name": vpc_name,
+ "admin_state_up": False,
+ "shared": False,
+ "tenant_id": "c1210485b2424d48804aad5d39c61b8f",
+ "id": "aaaa-netid"
+ }})
+
+ def create_eip(self, t, stack, resource_name):
+ rsrc = eip.ElasticIp(resource_name,
+ t['Resources'][resource_name],
+ stack)
+ self.assertEqual(None, rsrc.validate())
+ scheduler.TaskRunner(rsrc.create)()
+ self.assertEqual((rsrc.CREATE, rsrc.COMPLETE), rsrc.state)
+ return rsrc
+
+ def create_association(self, t, stack, resource_name):
+ rsrc = eip.ElasticIpAssociation(resource_name,
+ t['Resources'][resource_name],
+ stack)
+ self.assertEqual(None, rsrc.validate())
+ scheduler.TaskRunner(rsrc.create)()
+ self.assertEqual((rsrc.CREATE, rsrc.COMPLETE), rsrc.state)
+ return rsrc
+
+ def mock_update_floatingip(self, port='the_nic'):
+ clients.neutronclient.Client.update_floatingip(
+ 'fc68ea2c-b60b-4b4f-bd82-94ec81110766',
+ {'floatingip': {'port_id': port}}).AndReturn(None)
+
+ def mock_create_gateway_attachment(self):
+ clients.neutronclient.Client.add_gateway_router(
+ 'bbbb', {'network_id': 'eeee'}).AndReturn(None)
+
+ def mock_create_floatingip(self):
+ clients.neutronclient.Client.list_networks(
+ **{'router:external': True}).AndReturn({'networks': [{
+ 'status': 'ACTIVE',
+ 'subnets': [],
+ 'name': 'nova',
+ 'router:external': True,
+ 'tenant_id': 'c1210485b2424d48804aad5d39c61b8f',
+ 'admin_state_up': True,
+ 'shared': True,
+ 'id': 'eeee'
+ }]})
+
+ clients.neutronclient.Client.create_floatingip({
+ 'floatingip': {'floating_network_id': u'eeee'}
+ }).AndReturn({'floatingip': {
+ "status": "ACTIVE",
+ "id": "fc68ea2c-b60b-4b4f-bd82-94ec81110766",
+ "floating_ip_address": "192.168.9.3"
+ }})
+
+ def mock_show_floatingip(self, refid):
+ clients.neutronclient.Client.show_floatingip(
+ refid,
+ ).AndReturn({'floatingip': {
+ 'router_id': None,
+ 'tenant_id': 'e936e6cd3e0b48dcb9ff853a8f253257',
+ 'floating_network_id': 'eeee',
+ 'fixed_ip_address': None,
+ 'floating_ip_address': '172.24.4.227',
+ 'port_id': None,
+ 'id': 'ffff'
+ }})
+
+ def mock_delete_floatingip(self):
+ id = 'fc68ea2c-b60b-4b4f-bd82-94ec81110766'
+ clients.neutronclient.Client.delete_floatingip(id).AndReturn(None)
+
+ def mock_list_ports(self):
+ clients.neutronclient.Client.list_ports(id='the_nic').AndReturn(
+ {"ports": [{
+ "status": "DOWN",
+ "binding:host_id": "null",
+ "name": "wp-NIC-yu7fc7l4g5p6",
+ "admin_state_up": True,
+ "network_id": "22c26451-cf27-4d48-9031-51f5e397b84e",
+ "tenant_id": "ecf538ec1729478fa1f97f1bf4fdcf7b",
+ "binding:vif_type": "ovs",
+ "device_owner": "",
+ "binding:capabilities": {"port_filter": True},
+ "mac_address": "fa:16:3e:62:2d:4f",
+ "fixed_ips": [{"subnet_id": "mysubnetid-70ec",
+ "ip_address": "192.168.9.2"}],
+ "id": "a000228d-b40b-4124-8394-a4082ae1b76b",
+ "security_groups": ["5c6f529d-3186-4c36-84c0-af28b8daac7b"],
+ "device_id": ""
+ }]})
+
+ def mock_list_subnets(self):
+ clients.neutronclient.Client.list_subnets(
+ id='mysubnetid-70ec').AndReturn(
+ {'subnets': [{
+ u'name': u'wp-Subnet-pyjm7bvoi4xw',
+ u'enable_dhcp': True,
+ u'network_id': u'aaaa-netid',
+ u'tenant_id': u'ecf538ec1729478fa1f97f1bf4fdcf7b',
+ u'dns_nameservers': [],
+ u'allocation_pools': [{u'start': u'192.168.9.2',
+ u'end': u'192.168.9.254'}],
+ u'host_routes': [],
+ u'ip_version': 4,
+ u'gateway_ip': u'192.168.9.1',
+ u'cidr': u'192.168.9.0/24',
+ u'id': u'2c339ccd-734a-4acc-9f64-6f0dfe427e2d'
+ }]})
+
+ def mock_router_for_vpc(self):
+ vpc_name = utils.PhysName('test_stack', 'the_vpc')
+ clients.neutronclient.Client.list_routers(name=vpc_name).AndReturn({
+ "routers": [{
+ "status": "ACTIVE",
+ "external_gateway_info": {
+ "network_id": "zzzz",
+ "enable_snat": True},
+ "name": vpc_name,
+ "admin_state_up": True,
+ "tenant_id": "3e21026f2dc94372b105808c0e721661",
+ "routes": [],
+ "id": "bbbb"
+ }]
+ })
+
+ def mock_keystone(self):
+ clients.OpenStackClients.keystone().AndReturn(
+ fakec.FakeKeystoneClient())
+
+ def test_neutron_eip(self):
+ eip.ElasticIp.nova().MultipleTimes().AndReturn(self.fc)
self.fc.servers.get('WebServer').AndReturn(self.fc.servers.list()[0])
- eip.ElasticIp.nova().AndReturn(self.fc)
+ self.fc.servers.get('WebServer')
self.m.ReplayAll()
- t = template_format.parse(eip_template_ipassoc)
+ t = template_format.parse(eip_template)
stack = utils.parse_stack(t)
rsrc = self.create_eip(t, stack, 'IPAddress')
- association = self.create_association(t, stack, 'IPAssoc')
- # TODO(sbaker), figure out why this is an empty string
- #self.assertEqual('', association.FnGetRefId())
+ try:
+ self.assertEqual('11.0.0.1', rsrc.FnGetRefId())
+ rsrc.refid = None
+ self.assertEqual('11.0.0.1', rsrc.FnGetRefId())
+
+ self.assertEqual('1', rsrc.FnGetAtt('AllocationId'))
+
+ self.assertRaises(resource.UpdateReplace,
+ rsrc.handle_update, {}, {}, {})
+
+ self.assertRaises(exception.InvalidTemplateAttribute,
+ rsrc.FnGetAtt, 'Foo')
+
+ finally:
+ rsrc.destroy()
+
+ self.m.VerifyAll()
+
+ def test_association_allocationid(self):
+ self.mock_keystone()
+ self.mock_create_gateway_attachment()
+ self.mock_show_network()
+ self.mock_router_for_vpc()
+
+ self.mock_create_floatingip()
+ self.mock_list_ports()
+ self.mock_list_subnets()
+
+ self.mock_show_floatingip('fc68ea2c-b60b-4b4f-bd82-94ec81110766')
+ self.mock_update_floatingip()
+
+ self.mock_update_floatingip(port=None)
+ self.mock_delete_floatingip()
+
+ self.m.ReplayAll()
+
+ t = template_format.parse(eip_template_ipassoc2)
+ stack = utils.parse_stack(t)
+
+ rsrc = self.create_eip(t, stack, 'the_eip')
+ association = self.create_association(t, stack, 'IPAssoc')
association.delete()
rsrc.delete()