Merge ~xavpaice/charm-openstack-service-checks/+git/charm-openstack-service-checks:addressinfo into ~canonical-bootstack/charm-openstack-service-checks:master

Proposed by Xav Paice
Status: Rejected
Rejected by: Haw Loeung
Proposed branch: ~xavpaice/charm-openstack-service-checks/+git/charm-openstack-service-checks:addressinfo
Merge into: ~canonical-bootstack/charm-openstack-service-checks:master
Diff against target: 289 lines (+252/-0)
6 files modified
actions.yaml (+6/-0)
actions/addressinfo (+1/-0)
actions/addressinfo.py (+190/-0)
tests/00-setup (+5/-0)
tests/99-autogen (+49/-0)
wheelhouse.txt (+1/-0)
Reviewer Review Type Date Requested Status
Drew Freiberger (community) Needs Information
Review via email: mp+341809@code.launchpad.net

Commit message

Add action addressinfo

To post a comment you must log in.
Revision history for this message
🤖 Canonical IS Merge Bot (canonical-is-mergebot) wrote :

This merge proposal is being monitored by mergebot. Change the status to Approved to merge.

Revision history for this message
Drew Freiberger (afreiberger) wrote :

Can't get this to build. If you've got a ~xavpaice release or a branch with the built charm, that'd be handy for testing.

Code looks good, I just can't get it to build for testing.

review: Needs Information
Revision history for this message
Xav Paice (xavpaice) wrote :

switching to WIP, doesn't build.

Unmerged commits

8378f2a... by Xav Paice

Add action addressinfo

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/actions.yaml b/actions.yaml
2new file mode 100644
3index 0000000..76a1cb9
4--- /dev/null
5+++ b/actions.yaml
6@@ -0,0 +1,6 @@
7+addressinfo:
8+ description: Collect information about a given IP address.
9+ params:
10+ ip:
11+ type: string
12+ description: IP address to collect info about
13diff --git a/actions/addressinfo b/actions/addressinfo
14new file mode 120000
15index 0000000..e9da941
16--- /dev/null
17+++ b/actions/addressinfo
18@@ -0,0 +1 @@
19+addressinfo.py
20\ No newline at end of file
21diff --git a/actions/addressinfo.py b/actions/addressinfo.py
22new file mode 100755
23index 0000000..7fa435b
24--- /dev/null
25+++ b/actions/addressinfo.py
26@@ -0,0 +1,190 @@
27+#!/usr/local/sbin/charm-env python3
28+#
29+# Copyright 2018 Canonical Ltd
30+#
31+# Licensed under the Apache License, Version 2.0 (the "License");
32+# you may not use this file except in compliance with the License.
33+# You may obtain a copy of the License at
34+#
35+# http://www.apache.org/licenses/LICENSE-2.0
36+#
37+# Unless required by applicable law or agreed to in writing, software
38+# distributed under the License is distributed on an "AS IS" BASIS,
39+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
40+# See the License for the specific language governing permissions and
41+# limitations under the License.
42+#
43+
44+import pprint
45+from reactive import service_checks
46+from openstack import connection, profile, exceptions
47+from charmhelpers.core.hookenv import (
48+ action_get,
49+ action_set,
50+ log,
51+ action_fail
52+ )
53+
54+
55+# copied directly from the docs for openstacksdk
56+def create_connection(auth_url, region, project_name, username, password):
57+ prof = profile.Profile()
58+ prof.set_region(profile.Profile.ALL, region)
59+ conn = connection.Connection(region_name=region, auth=dict(
60+ auth_url=auth_url,
61+ username=username,
62+ password=password,
63+ project_name=project_name),
64+ )
65+ return conn
66+
67+
68+def port_from_ip(conn, ip):
69+ for port_obj in conn.network.ports():
70+ port = port_obj.to_dict()
71+ for fixed_ip in port['fixed_ips']:
72+ if fixed_ip['ip_address'] == ip:
73+ return port
74+ return None
75+
76+
77+def project_id_to_name(conn, project_id):
78+ #v2
79+ project_obj = conn.identity.find_tenant(project_id)
80+ if project_obj:
81+ project = project_obj.to_dict()
82+ return project['name']
83+ return None
84+
85+
86+def instance_info(conn, port_id):
87+ log('getting floating_ip target info.')
88+ port = conn.network.get_port(port_id).to_dict()
89+ # TODO exception handling, ResourceNotFound
90+ instance = conn.compute.get_server(port['device_id']).to_dict()
91+ target_info = {
92+ 'instance_name': instance['name'],
93+ 'instance_virsh_name': instance['instance_name'],
94+ 'instance_id': instance['id'],
95+ 'hypervisor_hostname': instance['hypervisor_hostname'],
96+ 'instance_port_id': port_id
97+ }
98+ sgrules = []
99+ for security_group in port['security_group_ids']:
100+ for rule in conn.network.security_group_rules(
101+ security_group_id=security_group
102+ ):
103+ sgrules.append(rule.to_dict())
104+ target_info['sgrules'] = sgrules
105+ tap_interface = 'tap' + port['id'][:11]
106+ target_info['PROTIP'] = ('"tcpdump -ni {}" on '
107+ '{}'.format(tap_interface,
108+ port['binding_host_id']))
109+ return target_info
110+
111+
112+def get_floatingip_mapping(conn, ip, floatingip_obj):
113+ mapping_info = {}
114+ mapping_info['router_id'] = floatingip_obj.router_id
115+ try:
116+ mapping_info['router_info'] = get_router_info(conn,
117+ floatingip_obj.router_id)
118+ except NameError:
119+ pass
120+ mapping_info['mapping'] = '{} <-> {}'.format(
121+ ip, floatingip_obj.fixed_ip_address
122+ )
123+ return mapping_info
124+
125+
126+def get_router_info(conn, router_id):
127+ log('getting router info')
128+ router_info = {}
129+ try:
130+ router = conn.network.get_router(router_id).to_dict()
131+ except exceptions.ResourceNotFound:
132+ router_info['router_id'] = 'Router not found'
133+ return router_info
134+ for agent_obj in conn.network.routers_hosting_l3_agents(router_id):
135+ agent = agent_obj.to_dict()
136+ if agent['is_alive']:
137+ agent_state = 'ALIVE'
138+ scheduled = True
139+ router_info['PROTIP'] = ('tcpdump inside the router namespace '
140+ '"ip netns exec qrouter-{} tcpdump -ln" '
141+ 'on {}'.format(router['id'],
142+ agent['host']))
143+ else:
144+ agent_state = 'DEAD'
145+ router_info['L3_Agent_host'] = agent['host']
146+ router_info['L3_Agent_state'] = agent_state
147+ if not scheduled:
148+ router_info['L3_Agent_host'] = ('Router {} does not appear to be '
149+ 'scheduled.'.format(router['id']))
150+ router_info['router_id'] = router_id
151+ return router_info
152+
153+
154+def address_info():
155+ output_json = {}
156+ ip = action_get("ip")
157+ if not ip:
158+ action_fail("Need an IP address supplied for this to be useful")
159+ output_json['ip'] = ip
160+ creds = service_checks.get_credentials()
161+ conn = create_connection(creds['auth_url'],
162+ creds['region'],
163+ creds['credentials_project'],
164+ creds['credentials_username'],
165+ creds['credentials_password']
166+ )
167+ # is it a floating ip?
168+ floatingip_obj = conn.network.find_ip(ip)
169+ if floatingip_obj and floatingip_obj.port_id is None:
170+ output_json['type'] = 'Unassociated Floating IP'
171+ else:
172+ if floatingip_obj and floatingip_obj.port_id is not None:
173+ # floating IP mapped to something, get more info
174+ output_json['mapping'] = get_floatingip_mapping(conn,
175+ ip,
176+ floatingip_obj)
177+ output_json['instance_info'] = instance_info(conn,
178+ floatingip_obj.port_id)
179+ output_json['router_info'] = get_router_info(conn,
180+ floatingip_obj.router_id)
181+ port = port_from_ip(conn, ip)
182+ if not port:
183+ action_fail('IP {} not found'.format(ip))
184+ output_json['type'] = port['device_owner']
185+ output_json['port_network_id'] = port['network_id']
186+ output_json['port_subnet_id'] = port['fixed_ips'][0]['subnet_id']
187+ output_json['project_name'] = project_id_to_name(conn,
188+ port['project_id'])
189+ output_json['project_id'] = port['project_id']
190+ output_json['port_id'] = port['id']
191+ output_json['mac_address'] = port['mac_address']
192+ if (port['device_owner'] == 'network:router_interface' or
193+ port['device_owner'] == 'network:router_gateway'):
194+ output_json['host'] = port['binding_host_id']
195+ try:
196+ output_json['router_info'] = get_router_info(conn,
197+ port['device_id'])
198+ except NameError:
199+ pass
200+ elif port['device_owner'] == 'network:dhcp':
201+ output_json['host'] = port['binding_host_id']
202+ elif port['device_owner'] == 'compute:None':
203+ instance = conn.compute.get_server(port['device_id'])
204+ output_json['host'] = instance.hypervisor_hostname
205+ output_json['virsh_name'] = instance.instance_name
206+ output_json['instance_id'] = instance.id
207+ output_json['instance_addresses'] = instance.addresses
208+ output_json['instance_info'] = instance_info(conn,
209+ port['id'])
210+ output_text = pprint.pformat(output_json)
211+ return output_text
212+
213+
214+if __name__ == '__main__':
215+ message = address_info()
216+ action_set({'message': message})
217diff --git a/tests/00-setup b/tests/00-setup
218new file mode 100755
219index 0000000..cd5b3a2
220--- /dev/null
221+++ b/tests/00-setup
222@@ -0,0 +1,5 @@
223+#!/bin/bash
224+
225+sudo add-apt-repository ppa:juju/stable -y
226+sudo apt-get update
227+sudo apt-get install amulet python3-requests -y
228diff --git a/tests/99-autogen b/tests/99-autogen
229new file mode 100755
230index 0000000..750f608
231--- /dev/null
232+++ b/tests/99-autogen
233@@ -0,0 +1,49 @@
234+#!/usr/bin/env python3
235+
236+import amulet
237+import requests
238+import unittest
239+
240+
241+class TestDeployment(unittest.TestCase):
242+ @classmethod
243+ def setUpClass(cls):
244+ cls.deployment = amulet.Deployment(series='trusty')
245+
246+ cls.deployment.add('openstack-service-checks')
247+
248+ try:
249+ cls.deployment.setup(timeout=900)
250+ cls.deployment.sentry.wait()
251+ except amulet.helpers.TimeoutError:
252+ amulet.raise_status(amulet.SKIP, msg="Environment wasn't stood up in time")
253+ except:
254+ raise
255+
256+ def test_case(self):
257+ # Now you can use self.deployment.sentry.unit[UNIT] to address each of
258+ # the units and perform more in-depth steps. You can also reference
259+ # the first unit as self.unit.
260+ # There are three test statuses that can be triggered with
261+ # amulet.raise_status():
262+ # - amulet.PASS
263+ # - amulet.FAIL
264+ # - amulet.SKIP
265+ # Each unit has the following methods:
266+ # - .info - An array of the information of that unit from Juju
267+ # - .file(PATH) - Get the details of a file on that unit
268+ # - .file_contents(PATH) - Get plain text output of PATH file from that unit
269+ # - .directory(PATH) - Get details of directory
270+ # - .directory_contents(PATH) - List files and folders in PATH on that unit
271+ # - .relation(relation, service:rel) - Get relation data from return service
272+ # add tests here to confirm service is up and working properly
273+ # For example, to confirm that it has a functioning HTTP server:
274+ # page = requests.get('http://{}'.format(self.unit.info['public-address']))
275+ # page.raise_for_status()
276+ # More information on writing Amulet tests can be found at:
277+ # https://jujucharms.com/docs/stable/tools-amulet
278+ pass
279+
280+
281+if __name__ == '__main__':
282+ unittest.main()
283diff --git a/wheelhouse.txt b/wheelhouse.txt
284new file mode 100644
285index 0000000..720dbc3
286--- /dev/null
287+++ b/wheelhouse.txt
288@@ -0,0 +1 @@
289+openstacksdk

Subscribers

People subscribed via source and target branches