Merge "Neutron resources observe reality implementation"

This commit is contained in:
Jenkins 2017-07-25 15:09:13 +00:00 committed by Gerrit Code Review
commit 13f1f0d9fa
12 changed files with 441 additions and 3 deletions

View File

@ -181,6 +181,13 @@ class Firewall(neutron.NeutronResource):
'firewall resource.')
return super(Firewall, self)._resolve_attribute(name)
def parse_live_resource_data(self, resource_properties, resource_data):
result = super(Firewall, self).parse_live_resource_data(
resource_properties, resource_data)
if self.SHARED in result:
result.pop(self.SHARED)
return result
class FirewallPolicy(neutron.NeutronResource):
"""A resource for the FirewallPolicy resource in Neutron FWaaS.

View File

@ -17,6 +17,7 @@ from heat.engine import constraints
from heat.engine import properties
from heat.engine.resources.openstack.neutron import neutron
from heat.engine import support
from heat.engine import translation
class Net(neutron.NeutronResource):
@ -168,6 +169,14 @@ class Net(neutron.NeutronResource):
),
}
def translation_rules(self, properties):
return [translation.TranslationRule(
properties,
translation.TranslationRule.RESOLVE,
[self.QOS_POLICY],
client_plugin=self.client_plugin(),
finder='get_qos_policy_id')]
def handle_create(self):
props = self.prepare_properties(
self.properties,
@ -177,8 +186,7 @@ class Net(neutron.NeutronResource):
qos_policy = props.pop(self.QOS_POLICY, None)
tags = props.pop(self.TAGS, [])
if qos_policy:
props['qos_policy_id'] = self.client_plugin().get_qos_policy_id(
qos_policy)
props['qos_policy_id'] = qos_policy
net = self.client().create_network({'network': props})['network']
self.resource_id_set(net['id'])
@ -191,6 +199,7 @@ class Net(neutron.NeutronResource):
def check_create_complete(self, *args):
attributes = self._show_resource()
self._store_config_default_properties(attributes)
return self.is_built(attributes)
def handle_delete(self):
@ -205,7 +214,7 @@ class Net(neutron.NeutronResource):
if prop_diff:
self.prepare_update_properties(prop_diff)
if self.DHCP_AGENT_IDS in prop_diff:
dhcp_agent_ids = prop_diff.pop(self.DHCP_AGENT_IDS, [])
dhcp_agent_ids = prop_diff.pop(self.DHCP_AGENT_IDS) or []
self._replace_dhcp_agents(dhcp_agent_ids)
if self.QOS_POLICY in prop_diff:
qos_policy = prop_diff.pop(self.QOS_POLICY)
@ -249,6 +258,21 @@ class Net(neutron.NeutronResource):
self.client_plugin().is_not_found(ex)):
raise
def parse_live_resource_data(self, resource_properties, resource_data):
result = super(Net, self).parse_live_resource_data(
resource_properties, resource_data)
result.pop(self.SHARED)
result[self.QOS_POLICY] = resource_data.get('qos_policy_id')
try:
dhcp = self.client().list_dhcp_agent_hosting_networks(
self.resource_id)
dhcp_agents = set([agent['id'] for agent in dhcp['agents']])
result.update({self.DHCP_AGENT_IDS: list(dhcp_agents)})
except self.client_plugin().exceptions.Forbidden:
# Just don't add dhcp_clients if we can't get values.
pass
return result
def resource_mapping():
return {

View File

@ -81,6 +81,16 @@ class NeutronResource(resource.Resource):
NeutronResource.merge_value_specs(props)
return props
def _store_config_default_properties(self, attrs):
"""A method for storing properties, which defaults stored in config.
A method allows to store properties default values, which cannot be
defined in schema in case of specifying in config file.
"""
if 'port_security_enabled' in attrs:
self.data_set('port_security_enabled',
attrs['port_security_enabled'])
@staticmethod
def merge_value_specs(props):
value_spec_props = props.pop('value_specs')
@ -175,3 +185,24 @@ class NeutronResource(resource.Resource):
self.client().replace_tag(resource_plural,
self.resource_id,
body)
def parse_live_resource_data(self, resource_properties, resource_data):
result = super(NeutronResource, self).parse_live_resource_data(
resource_properties, resource_data)
if 'value_specs' in self.properties.keys():
result.update({self.VALUE_SPECS: {}})
for key in self.properties.get(self.VALUE_SPECS):
if key in resource_data:
result[self.VALUE_SPECS][key] = resource_data.get(key)
# We already get real `port_security_enabled` from
# super().parse_live_resource_data above, so just check and remove
# if that's same value as old port value.
if 'port_security_enabled' in self.properties.keys():
old_port = bool(self.data().get(self.PORT_SECURITY_ENABLED))
new_port = resource_data.get(self.PORT_SECURITY_ENABLED)
if old_port == new_port:
del result[self.PORT_SECURITY_ENABLED]
return result

View File

@ -490,8 +490,19 @@ class Port(neutron.NeutronResource):
if self.REPLACEMENT_POLICY in props:
del(props[self.REPLACEMENT_POLICY])
def _store_config_default_properties(self, attrs):
"""A method for storing properties default values.
A method allows to store properties default values, which cannot be
defined in schema in case of specifying in config file.
"""
super(Port, self)._store_config_default_properties(attrs)
if self.VNIC_TYPE in attrs:
self.data_set(self.VNIC_TYPE, attrs[self.VNIC_TYPE])
def check_create_complete(self, *args):
attributes = self._show_resource()
self._store_config_default_properties(attributes)
return self.is_built(attributes)
def handle_delete(self):
@ -502,6 +513,20 @@ class Port(neutron.NeutronResource):
else:
return True
def parse_live_resource_data(self, resource_properties, resource_data):
result = super(Port, self).parse_live_resource_data(
resource_properties, resource_data)
result[self.QOS_POLICY] = resource_data.get('qos_policy_id')
result.pop(self.MAC_ADDRESS)
fixed_ips = resource_data.get(self.FIXED_IPS) or []
if fixed_ips:
result.update({self.FIXED_IPS: []})
for fixed_ip in fixed_ips:
result[self.FIXED_IPS].append(
{self.FIXED_IP_SUBNET: fixed_ip.get('subnet_id'),
self.FIXED_IP_IP_ADDRESS: fixed_ip.get('ip_address')})
return result
def _resolve_attribute(self, name):
if self.resource_id is None:
return

View File

@ -170,6 +170,24 @@ class ProviderNet(net.Net):
self.client().update_network(self.resource_id,
{'network': prop_diff})
def parse_live_resource_data(self, resource_properties, resource_data):
# this resource should not have super in case of we don't need to
# parse Net resource properties.
result = {}
provider_keys = [self.PROVIDER_NETWORK_TYPE,
self.PROVIDER_PHYSICAL_NETWORK,
self.PROVIDER_SEGMENTATION_ID]
for key in provider_keys:
result[key] = resource_data.get('provider:%s' % key)
result[self.ROUTER_EXTERNAL] = resource_data.get('router:external')
provider_keys.append(self.ROUTER_EXTERNAL)
provider_keys.append(self.SHARED)
for key in set(self.PROPERTIES) - set(provider_keys):
if key in resource_data:
result[key] = resource_data.get(key)
return result
def resource_mapping():
return {

View File

@ -354,6 +354,27 @@ class Router(neutron.NeutronResource):
self.client().add_router_to_l3_agent(
l3_agent_id, {'router_id': self.resource_id})
def parse_live_resource_data(self, resource_properties, resource_data):
result = super(Router, self).parse_live_resource_data(
resource_properties, resource_data)
try:
ret = self.client().list_l3_agent_hosting_routers(self.resource_id)
if ret:
result[self.L3_AGENT_IDS] = list(
agent['id'] for agent in ret['agents'])
except self.client_plugin().exceptions.Forbidden:
# Just pass if forbidden
pass
gateway = resource_data.get(self.EXTERNAL_GATEWAY)
if gateway is not None:
result[self.EXTERNAL_GATEWAY] = {
self.EXTERNAL_GATEWAY_NETWORK: gateway.get('network_id'),
self.EXTERNAL_GATEWAY_ENABLE_SNAT: gateway.get('enable_snat')
}
return result
class RouterInterface(neutron.NeutronResource):
"""A resource for managing Neutron router interfaces.

View File

@ -11,6 +11,7 @@
# License for the specific language governing permissions and limitations
# under the License.
import mock
from neutronclient.common import exceptions
from neutronclient.v2_0 import client as neutronclient
from oslo_config import cfg
@ -271,6 +272,37 @@ class FirewallTest(common.HeatTestCase):
rsrc.handle_update(update_snippet, {}, prop_diff)
self.m.VerifyAll()
def test_get_live_state(self):
rsrc = self.create_firewall(value_specs=True)
rsrc.client().show_firewall = mock.Mock(return_value={
'firewall': {
'status': 'ACTIVE',
'router_ids': ['router_1', 'router_2'],
'name': 'firewall-firewall-pwakkqdrcl7z',
'admin_state_up': True,
'tenant_id': 'df49ea64e87c43a792a510698364f03e',
'firewall_policy_id': '680eb26d-3eea-40be-b484-1476e4c7c1b3',
'id': '11425cd4-41b6-4fd4-97aa-17629c63de61',
'description': ''
}
})
self.m.ReplayAll()
scheduler.TaskRunner(rsrc.create)()
reality = rsrc.get_live_state(rsrc.properties)
expected = {
'value_specs': {
'router_ids': ['router_1', 'router_2']
},
'name': 'firewall-firewall-pwakkqdrcl7z',
'admin_state_up': True,
'firewall_policy_id': '680eb26d-3eea-40be-b484-1476e4c7c1b3',
'description': ''
}
self.assertEqual(expected, reality)
self.m.VerifyAll()
class FirewallPolicyTest(common.HeatTestCase):

View File

@ -285,3 +285,67 @@ class NeutronNetTest(common.HeatTestCase):
rsrc.state_set(rsrc.CREATE, rsrc.COMPLETE, 'to delete again')
scheduler.TaskRunner(rsrc.delete)()
self.assertEqual((rsrc.DELETE, rsrc.COMPLETE), rsrc.state)
def test_net_get_live_state(self):
tmpl = """
heat_template_version: 2015-10-15
resources:
net:
type: OS::Neutron::Net
properties:
value_specs:
'test:property': test_value
"""
t = template_format.parse(tmpl)
stack = utils.parse_stack(t)
show_net = self.patchobject(neutronclient.Client, 'show_network')
show_net.return_value = {'network': {'status': 'ACTIVE'}}
self.patchobject(neutronclient.Client,
'list_dhcp_agent_hosting_networks',
return_value={'agents': [{'id': '1111'}]})
self.patchobject(neutronclient.Client, 'create_network',
return_value={"network": {
"status": "BUILD",
"subnets": [],
"qos_policy_id": "some",
"name": "name",
"admin_state_up": True,
"shared": True,
"tenant_id": "c1210485b2424d48804aad5d39c61b8f",
"id": "fc68ea2c-b60b-4b4f-bd82-94ec81110766",
"mtu": 0
}})
rsrc = self.create_net(t, stack, 'net')
network_resp = {
'name': 'net1-net-wkkl2vwupdee',
'admin_state_up': True,
'tenant_id': '30f466e3d14b4251853899f9c26e2b66',
'mtu': 0,
'router:external': False,
'port_security_enabled': True,
'shared': False,
'qos_policy_id': 'some',
'id': u'5a4bb8a0-5077-4f8a-8140-5430370020e6',
'test:property': 'test_value_resp'
}
show_net.return_value = {'network': network_resp}
reality = rsrc.get_live_state(rsrc.properties)
expected = {
'name': 'net1-net-wkkl2vwupdee',
'admin_state_up': True,
'qos_policy': "some",
'value_specs': {
'test:property': 'test_value_resp'
},
'port_security_enabled': True,
'dhcp_agent_ids': ['1111']
}
self.assertEqual(set(expected.keys()), set(reality.keys()))
for key in expected:
if key == 'dhcp_agent_ids':
self.assertEqual(set(expected[key]), set(reality[key]))
continue
self.assertEqual(expected[key], reality[key])

View File

@ -732,6 +732,68 @@ class NeutronPortTest(common.HeatTestCase):
mock.call(existing_rsrc.resource_id, expected_existing_props),
mock.call(prev_rsrc.resource_id, expected_prev_props)])
def test_port_get_live_state(self):
t = template_format.parse(neutron_port_template)
t['resources']['port']['properties']['value_specs'] = {
'binding:vif_type': 'test'}
stack = utils.parse_stack(t)
port = stack['port']
resp = {'port': {
'status': 'DOWN',
'binding:host_id': '',
'name': 'flip-port-xjbal77qope3',
'allowed_address_pairs': [],
'admin_state_up': True,
'network_id': 'd6859535-efef-4184-b236-e5fcae856e0f',
'dns_name': '',
'extra_dhcp_opts': [],
'mac_address': 'fa:16:3e:fe:64:79',
'qos_policy_id': 'some',
'dns_assignment': [],
'binding:vif_details': {},
'binding:vif_type': 'unbound',
'device_owner': '',
'tenant_id': '30f466e3d14b4251853899f9c26e2b66',
'binding:profile': {},
'port_security_enabled': True,
'binding:vnic_type': 'normal',
'fixed_ips': [
{'subnet_id': '02d9608f-8f30-4611-ad02-69855c82457f',
'ip_address': '10.0.3.4'}],
'id': '829bf5c1-b59c-40ad-80e3-ea15a93879f3',
'security_groups': ['c276247f-50fd-4289-862a-80fb81a55de1'],
'device_id': ''}
}
port.client().show_port = mock.MagicMock(return_value=resp)
port.resource_id = '1234'
port._data = {}
port.data_set = mock.Mock()
reality = port.get_live_state(port.properties)
expected = {
'name': 'flip-port-xjbal77qope3',
'allowed_address_pairs': [],
'admin_state_up': True,
'device_owner': '',
'port_security_enabled': True,
'binding:vnic_type': 'normal',
'fixed_ips': [
{'subnet': '02d9608f-8f30-4611-ad02-69855c82457f',
'ip_address': '10.0.3.4'}],
'security_groups': ['c276247f-50fd-4289-862a-80fb81a55de1'],
'device_id': '',
'dns_name': '',
'qos_policy': 'some',
'value_specs': {'binding:vif_type': 'unbound'}
}
self.assertEqual(set(expected.keys()), set(reality.keys()))
for key in expected:
self.assertEqual(expected[key], reality[key])
class UpdatePortTest(common.HeatTestCase):
scenarios = [

View File

@ -13,6 +13,7 @@
import copy
import mock
from neutronclient.common import exceptions as qe
from neutronclient.v2_0 import client as neutronclient
@ -190,3 +191,36 @@ class NeutronProviderNetTest(common.HeatTestCase):
# no prop_diff
self.assertIsNone(rsrc.handle_update(update_snippet, {}, {}))
self.m.VerifyAll()
def test_get_live_state(self):
rsrc = self.create_provider_net()
rsrc.client().show_network = mock.Mock(return_value={
'network': {
'status': 'ACTIVE',
'subnets': [],
'availability_zone_hints': [],
'availability_zones': [],
'name': 'prov-provider-nhalkd5xftp3',
'provider:physical_network': 'public',
'admin_state_up': True,
'tenant_id': 'df49ea64e87c43a792a510698364f03e',
'mtu': 0,
'router:external': False,
'port_security_enabled': True,
'shared': True,
'provider:network_type': 'flat',
'id': 'af216806-4462-4c68-bfa4-9580857e71c3',
'provider:segmentation_id': None}})
reality = rsrc.get_live_state(rsrc.properties)
expected = {
'name': 'prov-provider-nhalkd5xftp3',
'physical_network': 'public',
'admin_state_up': True,
'network_type': 'flat',
'port_security_enabled': True,
'segmentation_id': None,
'router_external': False
}
self.assertEqual(expected, reality)

View File

@ -807,3 +807,65 @@ class NeutronRouterTest(common.HeatTestCase):
rsrc = self.create_router(t, stack, 'router')
self._assert_mock_call_create_with_router_gw()
self.assertIsNone(scheduler.TaskRunner(rsrc.delete)())
def test_router_get_live_state(self):
tmpl = """
heat_template_version: 2015-10-15
resources:
router:
type: OS::Neutron::Router
properties:
external_gateway_info:
network: public
enable_snat: true
value_specs:
test_value_spec: spec_value
"""
t = template_format.parse(tmpl)
stack = utils.parse_stack(t)
rsrc = stack['router']
router_resp = {
'status': 'ACTIVE',
'external_gateway_info': {
'network_id': '1ede231a-0b46-40fc-ab3b-8029446d0d1b',
'enable_snat': True,
'external_fixed_ips': [
{'subnet_id': '8eea1723-6de7-4255-9f8a-a0ce0db8b995',
'ip_address': '10.0.3.3'}]
},
'name': 'er-router-naqzmqnzk4ej',
'admin_state_up': True,
'tenant_id': '30f466e3d14b4251853899f9c26e2b66',
'distributed': False,
'routes': [],
'ha': False,
'id': 'b047ff06-487d-48d7-a735-a54e2fd836c2',
'test_value_spec': 'spec_value'
}
rsrc.client().show_router = mock.MagicMock(
return_value={'router': router_resp})
rsrc.client().list_l3_agent_hosting_routers = mock.MagicMock(
return_value={'agents': [{'id': '1234'}, {'id': '5678'}]})
reality = rsrc.get_live_state(rsrc.properties)
expected = {
'external_gateway_info': {
'network': '1ede231a-0b46-40fc-ab3b-8029446d0d1b',
'enable_snat': True
},
'name': 'er-router-naqzmqnzk4ej',
'admin_state_up': True,
'value_specs': {
'test_value_spec': 'spec_value'
},
'l3_agent_ids': ['1234', '5678']
}
self.assertEqual(set(expected.keys()), set(reality.keys()))
for key in expected:
if key == 'external_gateway_info':
for info in expected[key]:
self.assertEqual(expected[key][info], reality[key][info])
self.assertEqual(expected[key], reality[key])

View File

@ -12,6 +12,7 @@
# under the License.
import copy
import mock
from neutronclient.common import exceptions as qe
from neutronclient.neutron import v2_0 as neutronV20
from neutronclient.v2_0 import client as neutronclient
@ -699,3 +700,60 @@ class NeutronSubnetTest(common.HeatTestCase):
self.assertEqual(hot_funcs.GetResource(stack, 'get_resource', 'net'),
rsrc.properties.get('network'))
self.assertIsNone(rsrc.properties.get('network_id'))
def test_subnet_get_live_state(self):
template = """
heat_template_version: 2015-04-30
resources:
net:
type: OS::Neutron::Net
properties:
name: test
subnet:
type: OS::Neutron::Subnet
properties:
network_id: { get_resource: net }
cidr: 10.0.0.0/25
value_specs:
test_value_spec: value_spec_value
"""
t = template_format.parse(template)
stack = utils.parse_stack(t)
rsrc = stack['subnet']
stack.create()
subnet_resp = {'subnet': {
'name': 'subnet-subnet-la5usdgifhrd',
'enable_dhcp': True,
'network_id': 'dffd43b3-6206-4402-87e6-8a16ddf3bd68',
'tenant_id': '30f466e3d14b4251853899f9c26e2b66',
'dns_nameservers': [],
'ipv6_ra_mode': None,
'allocation_pools': [{'start': '10.0.0.2', 'end': '10.0.0.126'}],
'gateway_ip': '10.0.0.1',
'ipv6_address_mode': None,
'ip_version': 4,
'host_routes': [],
'prefixlen': None,
'cidr': '10.0.0.0/25',
'id': 'b255342b-31b7-4674-8ea4-a144bca658b0',
'subnetpool_id': None,
'test_value_spec': 'value_spec_value'}
}
rsrc.client().show_subnet = mock.MagicMock(return_value=subnet_resp)
rsrc.resource_id = '1234'
reality = rsrc.get_live_state(rsrc.properties)
expected = {
'name': 'subnet-subnet-la5usdgifhrd',
'enable_dhcp': True,
'dns_nameservers': [],
'allocation_pools': [{'start': '10.0.0.2', 'end': '10.0.0.126'}],
'gateway_ip': '10.0.0.1',
'host_routes': [],
'value_specs': {'test_value_spec': 'value_spec_value'}
}
self.assertEqual(set(expected.keys()), set(reality.keys()))
for key in expected:
self.assertEqual(expected[key], reality[key])