8a226a6221b256284fc8e134cf83990e707cf550
[openstack-build/neutron-build.git] / neutron / agent / metadata / namespace_proxy.py
1 # Copyright 2012 New Dream Network, LLC (DreamHost)
2 #
3 #    Licensed under the Apache License, Version 2.0 (the "License"); you may
4 #    not use this file except in compliance with the License. You may obtain
5 #    a copy of the License at
6 #
7 #         http://www.apache.org/licenses/LICENSE-2.0
8 #
9 #    Unless required by applicable law or agreed to in writing, software
10 #    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11 #    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12 #    License for the specific language governing permissions and limitations
13 #    under the License.
14
15 import httplib2
16 from oslo_config import cfg
17 from oslo_log import log as logging
18 from oslo_service import wsgi as base_wsgi
19 from oslo_utils import encodeutils
20 import six
21 import six.moves.urllib.parse as urlparse
22 import webob
23
24 from neutron._i18n import _, _LE
25 from neutron.agent.linux import daemon
26 from neutron.agent.linux import utils as agent_utils
27 from neutron.common import config
28 from neutron.common import exceptions
29 from neutron.common import utils
30 from neutron import wsgi
31
32 LOG = logging.getLogger(__name__)
33
34
35 class NetworkMetadataProxyHandler(object):
36     """Proxy AF_INET metadata request through Unix Domain socket.
37
38     The Unix domain socket allows the proxy access resource that are not
39     accessible within the isolated tenant context.
40     """
41
42     def __init__(self, network_id=None, router_id=None):
43         self.network_id = network_id
44         self.router_id = router_id
45
46         if network_id is None and router_id is None:
47             raise exceptions.NetworkIdOrRouterIdRequiredError()
48
49     @webob.dec.wsgify(RequestClass=base_wsgi.Request)
50     def __call__(self, req):
51         LOG.debug("Request: %s", req)
52         try:
53             return self._proxy_request(req.remote_addr,
54                                        req.method,
55                                        req.path_info,
56                                        req.query_string,
57                                        req.body)
58         except Exception:
59             LOG.exception(_LE("Unexpected error."))
60             msg = _('An unknown error has occurred. '
61                     'Please try your request again.')
62             explanation = six.text_type(msg)
63             return webob.exc.HTTPInternalServerError(explanation=explanation)
64
65     def _proxy_request(self, remote_address, method, path_info,
66                        query_string, body):
67         headers = {
68             'X-Forwarded-For': remote_address,
69         }
70
71         if self.router_id:
72             headers['X-Neutron-Router-ID'] = self.router_id
73         else:
74             headers['X-Neutron-Network-ID'] = self.network_id
75
76         url = urlparse.urlunsplit((
77             'http',
78             '169.254.169.254',  # a dummy value to make the request proper
79             path_info,
80             query_string,
81             ''))
82
83         h = httplib2.Http()
84         resp, content = h.request(
85             url,
86             method=method,
87             headers=headers,
88             body=body,
89             connection_type=agent_utils.UnixDomainHTTPConnection)
90
91         if resp.status == 200:
92             LOG.debug(resp)
93             LOG.debug(encodeutils.safe_decode(content, errors='replace'))
94             response = webob.Response()
95             response.status = resp.status
96             response.headers['Content-Type'] = resp['content-type']
97             response.body = wsgi.encode_body(content)
98             return response
99         elif resp.status == 400:
100             return webob.exc.HTTPBadRequest()
101         elif resp.status == 404:
102             return webob.exc.HTTPNotFound()
103         elif resp.status == 409:
104             return webob.exc.HTTPConflict()
105         elif resp.status == 500:
106             msg = _(
107                 'Remote metadata server experienced an internal server error.'
108             )
109             LOG.debug(msg)
110             explanation = six.text_type(msg)
111             return webob.exc.HTTPInternalServerError(explanation=explanation)
112         else:
113             raise Exception(_('Unexpected response code: %s') % resp.status)
114
115
116 class ProxyDaemon(daemon.Daemon):
117     def __init__(self, pidfile, port, network_id=None, router_id=None,
118                  user=None, group=None, watch_log=True):
119         uuid = network_id or router_id
120         super(ProxyDaemon, self).__init__(pidfile, uuid=uuid, user=user,
121                                          group=group, watch_log=watch_log)
122         self.network_id = network_id
123         self.router_id = router_id
124         self.port = port
125
126     def run(self):
127         handler = NetworkMetadataProxyHandler(
128             self.network_id,
129             self.router_id)
130         proxy = wsgi.Server('neutron-network-metadata-proxy')
131         proxy.start(handler, self.port)
132
133         # Drop privileges after port bind
134         super(ProxyDaemon, self).run()
135
136         proxy.wait()
137
138
139 def main():
140     opts = [
141         cfg.StrOpt('network_id',
142                    help=_('Network that will have instance metadata '
143                           'proxied.')),
144         cfg.StrOpt('router_id',
145                    help=_('Router that will have connected instances\' '
146                           'metadata proxied.')),
147         cfg.StrOpt('pid_file',
148                    help=_('Location of pid file of this process.')),
149         cfg.BoolOpt('daemonize',
150                     default=True,
151                     help=_('Run as daemon.')),
152         cfg.PortOpt('metadata_port',
153                     default=9697,
154                     help=_("TCP Port to listen for metadata server "
155                            "requests.")),
156         cfg.StrOpt('metadata_proxy_socket',
157                    default='$state_path/metadata_proxy',
158                    help=_('Location of Metadata Proxy UNIX domain '
159                           'socket')),
160         cfg.StrOpt('metadata_proxy_user',
161                    help=_("User (uid or name) running metadata proxy after "
162                           "its initialization")),
163         cfg.StrOpt('metadata_proxy_group',
164                    help=_("Group (gid or name) running metadata proxy after "
165                           "its initialization")),
166         cfg.BoolOpt('metadata_proxy_watch_log',
167                     default=True,
168                     help=_("Watch file log. Log watch should be disabled when "
169                            "metadata_proxy_user/group has no read/write "
170                            "permissions on metadata proxy log file.")),
171     ]
172
173     cfg.CONF.register_cli_opts(opts)
174     # Don't get the default configuration file
175     cfg.CONF(project='neutron', default_config_files=[])
176     config.setup_logging()
177     utils.log_opt_values(LOG)
178
179     proxy = ProxyDaemon(cfg.CONF.pid_file,
180                         cfg.CONF.metadata_port,
181                         network_id=cfg.CONF.network_id,
182                         router_id=cfg.CONF.router_id,
183                         user=cfg.CONF.metadata_proxy_user,
184                         group=cfg.CONF.metadata_proxy_group,
185                         watch_log=cfg.CONF.metadata_proxy_watch_log)
186
187     if cfg.CONF.daemonize:
188         proxy.start()
189     else:
190         proxy.run()