Skip to content

Commit ff14575

Browse files
author
ajoino
authored
Merge pull request #14 from arrowhead-f/decouple-consumer
Decouple consumer
2 parents eeafeee + 491a5f3 commit ff14575

29 files changed

+715
-715
lines changed

arrowhead_client/__init__.py

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +0,0 @@
1-
#!/usr/bin/env python
2-
3-
from . import configuration
4-
from .system import ArrowheadSystem
5-
from .system import ConsumerSystem
6-
from .system import ProviderSystem
7-
from .service import ConsumedHttpService as ConsumedService
8-
from .service import ProvidedHttpService as ProvidedService
9-
10-
config = configuration.set_config()
11-
12-
#TODO: This line should be removed and the certificates should be added
13-
# to the list of trusted certificates.
14-
import requests
15-
from requests.packages.urllib3.exceptions import InsecureRequestWarning
16-
requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
17-

arrowhead_client/api.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
from arrowhead_client.configuration import config
2+
from arrowhead_client.application import ArrowheadApplication
3+
from arrowhead_client.system import ArrowheadSystem
4+
from arrowhead_client.consumer import Consumer
5+
from arrowhead_client.provider import Provider
6+
from arrowhead_client.logs import get_logger
7+
8+
9+
10+
class ArrowheadHttpApplication(ArrowheadApplication):
11+
def __init__(self,
12+
system_name: str,
13+
address: str,
14+
port: int,
15+
authentication_info: str = '',
16+
keyfile: str = '',
17+
certfile: str = ''):
18+
super().__init__(
19+
ArrowheadSystem(system_name, address, port, authentication_info),
20+
Consumer(keyfile, certfile),
21+
Provider(),
22+
get_logger(system_name, 'debug'),
23+
config,
24+
keyfile=keyfile,
25+
certfile=certfile
26+
)
27+
self._logger.info(f'{self.__class__.__name__} initialized at {self.system.address}:{self.system.port}')
28+
#TODO: This line is a hack and needs to be fixed

arrowhead_client/application.py

Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
1+
from __future__ import annotations
2+
from typing import Any, Dict, Tuple
3+
from gevent import pywsgi # type: ignore
4+
from arrowhead_client.consumer import Consumer
5+
from arrowhead_client.provider import Provider
6+
from arrowhead_client.service import Service
7+
from arrowhead_client.core_services import core_service
8+
from arrowhead_client.system import ArrowheadSystem
9+
from arrowhead_client import core_service_forms as forms
10+
from arrowhead_client import core_service_responses as responses
11+
12+
13+
class ArrowheadApplication():
14+
def __init__(self,
15+
system: ArrowheadSystem,
16+
consumer: Consumer,
17+
provider: Provider,
18+
logger: Any,
19+
config: Dict,
20+
server: Any = None, # type: ignore
21+
keyfile: str = '',
22+
certfile: str = '', ):
23+
self.system = system
24+
self.consumer = consumer
25+
self.provider = provider
26+
self._logger = logger
27+
self.keyfile = keyfile
28+
self.certfile = certfile
29+
self.config = config
30+
#TODO: Remove this hardcodedness
31+
self.server = pywsgi.WSGIServer((self.system.address, self.system.port), self.provider.app,
32+
keyfile=self.keyfile, certfile=self.certfile,
33+
log=self._logger)
34+
self._core_system_setup()
35+
self.add_provided_service = self.provider.add_provided_service
36+
37+
@property
38+
def cert(self) -> Tuple[str, str]:
39+
return self.certfile, self.keyfile
40+
41+
'''
42+
@classmethod
43+
def from_cfg(cls, properties_file: str) -> ArrowheadHttpApplication:
44+
""" Creates a BaseArrowheadSystem from a descriptor file """
45+
46+
# Parse configuration file
47+
config = configparser.ConfigParser()
48+
with open(properties_file, 'r') as properties:
49+
config.read_file(properties)
50+
config_dict = {k: v for k, v in config.items('SYSTEM')}
51+
52+
# Create class instance
53+
system = cls(**config_dict)
54+
55+
return system
56+
'''
57+
58+
def _core_system_setup(self) -> None:
59+
service_registry = ArrowheadSystem(
60+
'service_registry',
61+
str(self.config['service_registry']['address']),
62+
int(self.config['service_registry']['port']),
63+
''
64+
)
65+
orchestrator = ArrowheadSystem(
66+
'orchestrator',
67+
str(self.config['orchestrator']['address']),
68+
int(self.config['orchestrator']['port']),
69+
'')
70+
71+
self._store_consumed_service(core_service('register'), service_registry, 'POST')
72+
self._store_consumed_service(core_service('unregister'), service_registry, 'DELETE')
73+
self._store_consumed_service(core_service('orchestration-service'), orchestrator, 'POST')
74+
75+
def consume_service(self, service_definition: str, **kwargs):
76+
return self.consumer.consume_service(service_definition, **kwargs)
77+
78+
def add_consumed_service(self,
79+
service_definition: str,
80+
http_method: str) -> None:
81+
""" Add orchestration rule for service definition """
82+
83+
orchestration_form = forms.OrchestrationForm(self.system.dto, service_definition)
84+
85+
orchestration_response = self.consume_service('orchestration-service',
86+
json=orchestration_form.dto,
87+
cert=self.cert,
88+
)
89+
#TODO: Handle orchestrator error codes
90+
91+
orchestration_payload = orchestration_response.json() # This might change with backend
92+
93+
(orchestrated_service, system), *_ = responses.handle_orchestration_response(orchestration_payload)
94+
95+
#TODO: Handle response with more than 1 service
96+
# Perhaps a list of consumed services for each service definition should be stored
97+
self._store_consumed_service(orchestrated_service, system, http_method)
98+
99+
def _store_consumed_service(self,
100+
service: Service,
101+
system: ArrowheadSystem,
102+
http_method: str) -> None:
103+
""" Register consumed services with the consumer """
104+
105+
self.consumer._consumed_services[service.service_definition] = (service, system, http_method)
106+
107+
def provided_service(self,
108+
service_definition: str,
109+
service_uri: str,
110+
interface: str,
111+
method: str):
112+
def wrapped_func(func):
113+
self.provider.add_provided_service(
114+
service_definition,
115+
service_uri,
116+
interface,
117+
http_method=method,
118+
view_func=func)
119+
return func
120+
return wrapped_func
121+
122+
def _register_service(self, service: Service):
123+
""" Registers the given service with service registry """
124+
125+
service_registration_form = forms.ServiceRegistrationForm(
126+
service_definition=service.service_definition,
127+
service_uri=service.service_uri,
128+
secure='CERTIFICATE',
129+
# TODO: secure should _NOT_ be hardcoded
130+
interfaces=service.interface.dto,
131+
provider_system=self.system.dto
132+
)
133+
134+
service_registration_response = self.consume_service(
135+
'register',
136+
json=service_registration_form.dto,
137+
)
138+
139+
print(service_registration_response.status_code)
140+
# TODO: Error handling
141+
142+
# TODO: Do logging
143+
144+
145+
def _register_all_services(self) -> None:
146+
""" Registers all services of the system. """
147+
for service, _ in self.provider.provided_services.values():
148+
self._register_service(service)
149+
150+
151+
def _unregister_service(self, service_definition: str) -> None:
152+
""" Unregisters the given service with the service registry. """
153+
154+
if service_definition not in self.provider.provided_services.keys():
155+
raise ValueError(f'{service_definition} not provided by {self}')
156+
157+
unregistration_payload = {
158+
'service_definition': service_definition,
159+
'system_name': self.system.system_name,
160+
'address': self.system.address,
161+
'port': self.system.port
162+
}
163+
164+
service_unregistration_response = self.consume_service(
165+
'unregister',
166+
params=unregistration_payload
167+
)
168+
169+
print(service_unregistration_response.status_code)
170+
171+
172+
def _unregister_all_services(self) -> None:
173+
""" Unregisters all services of the system """
174+
175+
for service_definition in self.provider.provided_services:
176+
self._unregister_service(service_definition)
177+
178+
179+
def run_forever(self) -> None:
180+
""" Start the server, publish all service, and run until interrupted. Then, unregister all services"""
181+
182+
import warnings
183+
warnings.simplefilter('ignore')
184+
185+
self._register_all_services()
186+
try:
187+
self._logger.info(f'Starting server')
188+
print('Started Arrowhead System')
189+
self.server.serve_forever()
190+
except KeyboardInterrupt:
191+
self._logger.info(f'Shutting down server')
192+
print('Shutting down Arrowhead system')
193+
self._unregister_all_services()
194+
finally:
195+
self._logger.info(f'Server shut down')
196+
197+
198+
"""
199+
def __enter__(self):
200+
'''Start server and register all services'''
201+
import warnings
202+
warnings.simplefilter('ignore')
203+
204+
print('Starting server')
205+
self.server.start()
206+
print('Registering services')
207+
self.register_all_services()
208+
209+
def __exit__(self, exc_type, exc_value, tb):
210+
'''Unregister all services and stop the server'''
211+
if exc_type != KeyboardInterrupt:
212+
print(f'Exception was raised:')
213+
print(exc_value)
214+
215+
print('\nSystem was stopped, unregistering services')
216+
self.unregister_all_services()
217+
print('Stopping server')
218+
self.server.stop()
219+
print('Shutdown completed')
220+
221+
return True
222+
"""
223+
if __name__ == '__main__':
224+
pass

arrowhead_client/configuration.py

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,8 @@
1-
#!/usr/bin/env python
2-
3-
from typing import Dict
4-
51
default_config = {
6-
'service_registry': {'address': '127.0.0.1', 'port': '8443'},
7-
'orchestrator': {'address': '127.0.0.1', 'port': '8441'},
8-
'eventhandler': {'address': '127.0.0.1', 'port': '8455'},
9-
'gatekeeper': {'address': '127.0.0.1', 'port': '8449'},
10-
'gateway': {'address': '127.0.0.1', 'port': '8453'}, }
11-
2+
'service_registry': {'address': '127.0.0.1', 'port': 8443},
3+
'orchestrator': {'address': '127.0.0.1', 'port': 8441},
4+
'eventhandler': {'address': '127.0.0.1', 'port': 8455},
5+
'gatekeeper': {'address': '127.0.0.1', 'port': 8449},
6+
'gateway': {'address': '127.0.0.1', 'port': 8453}, }
127

13-
def set_config() -> Dict[str, Dict[str, str]]:
14-
return default_config
8+
config = default_config

arrowhead_client/consumer.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
from __future__ import annotations
2+
from dataclasses import dataclass
3+
from typing import Tuple, Dict, Union
4+
5+
import requests as backend
6+
from arrowhead_client.service import Service
7+
from arrowhead_client.system import ArrowheadSystem as System
8+
9+
@dataclass
10+
class Consumer():
11+
""" Class to create Arrowhead consumer systems """
12+
13+
#TODO: Add all arguments instead of using *args
14+
def __init__(self, keyfile, certfile) -> None:
15+
self.keyfile = keyfile
16+
self.certfile = certfile
17+
self._consumed_services: Dict[str, Tuple[Service, System, str]] = {}
18+
19+
def consume_service(self, service_definition: str, **kwargs) -> backend.Response:
20+
""" Consume registered service """
21+
# TODO: Add error handling for the case where the service is not
22+
# registered in _consumed_services
23+
24+
uri, http_method = self._service_uri(service_definition)
25+
26+
service_response = backend.request(http_method, uri, verify=False, **kwargs)
27+
28+
return service_response
29+
30+
#TODO: type ignore above should be removed when mypy issue
31+
# https://github.com/python/mypy/issues/6799 is fixed
32+
33+
def _service_uri(self, service_definition: str) -> Tuple[str, str]:
34+
service, system, http_method = self._consumed_services[service_definition]
35+
uri = f'https://{system.authority}/{service.service_uri}'
36+
37+
return uri, http_method
38+
39+
def _extract_payload(self, service_response, interface) -> Union[Dict, str]:
40+
if interface.payload.upper() == 'JSON':
41+
return service_response.json()
42+
43+
return service_response.text
44+

arrowhead_client/core_service_forms.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,9 @@
33

44
from abc import ABC, abstractmethod
55
from dataclasses import dataclass
6-
from typing import List, Optional, Dict, Union, Any, Sequence, Mapping
6+
from typing import List, Optional, Dict, Union, Sequence, Mapping
77

88
from . import utils
9-
from .utils import ServiceInterface
109

1110

1211
class BaseServiceForm(ABC):
@@ -43,8 +42,8 @@ class ServiceQueryForm(CoreSystemServiceForm):
4342
ping_providers: bool = True
4443

4544
def __post_init__(self):
46-
self.interface_requirements = utils.handle_requirements(self.interface_requirements)
47-
self.security_requirements = utils.handle_requirements(self.security_requirements)
45+
self.interface_requirements = utils.uppercase_strings_in_list(self.interface_requirements)
46+
self.security_requirements = utils.uppercase_strings_in_list(self.security_requirements)
4847

4948

5049
@dataclass

0 commit comments

Comments
 (0)