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

Subscribers

People subscribed via source and target branches