diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..d811e1a0662f61b1ebca4bcad0d52810dd6ef940 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,50 @@ +# Copyright 2018 Telefonica S.A. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +FROM ubuntu:18.04 + +# Set the working directory to /app +WORKDIR /app/EE + +# Libraries used by the base osm defined ee +RUN apt-get update && apt-get install -y git python3 python3-pip \ + && python3 -m pip install --upgrade pip \ + && python3 -m pip install -U grpcio-tools \ + && python3 -m pip install -U grpclib \ + && python3 -m pip install -U PyYAML + +# Copy the current directory contents into the container at /app/LCM +ADD . /app/EE + +# Install as module +RUN python3 -m pip install -e /app/EE + +# Install SNMP Generator and its dependencies +#RUN apt-get install -y python3-pip unzip build-essential libsnmp-dev wget curl +#RUN curl -s https://storage.googleapis.com/golang/go1.11.8.linux-amd64.tar.gz| tar -v -C /usr/local -xz +#ENV PATH $PATH:/usr/local/go/bin +#ENV GOPATH /go +#RUN go get github.com/go-logfmt/logfmt \ +# && go get github.com/go-kit/kit/log +#RUN wget -q https://github.com/prometheus/snmp_exporter/archive/v0.17.0.tar.gz -P /tmp/ \ +# && tar -C /tmp -xf /tmp/v0.17.0.tar.gz \ +# && (cd /tmp/snmp_exporter-0.17.0/generator && go build) \ +# && cp /tmp/snmp_exporter-0.17.0/generator/generator /usr/local/bin/snmp_generator + +EXPOSE 50051 + +#CMD python3 -m osm_ee.frontend_server +# For development +CMD [ "bash", "-c", "while true; do /app/EE/osm_ee/scripts/ee_start.sh ; sleep 5; done" ] diff --git a/osm_ee/__init__.py b/osm_ee/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/osm_ee/__pycache__/__init__.cpython-36.pyc b/osm_ee/__pycache__/__init__.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..678249a89339d67be2f4fa478230d00ac69bbca2 Binary files /dev/null and b/osm_ee/__pycache__/__init__.cpython-36.pyc differ diff --git a/osm_ee/__pycache__/base_ee.cpython-36.pyc b/osm_ee/__pycache__/base_ee.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f00b8d440a8d55e6f5b05b748e8ff56771be930b Binary files /dev/null and b/osm_ee/__pycache__/base_ee.cpython-36.pyc differ diff --git a/osm_ee/__pycache__/exceptions.cpython-36.pyc b/osm_ee/__pycache__/exceptions.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b3522d8b4e6dc9a9f7fb6042923a442955a86ca0 Binary files /dev/null and b/osm_ee/__pycache__/exceptions.cpython-36.pyc differ diff --git a/osm_ee/__pycache__/frontend_client.cpython-36.pyc b/osm_ee/__pycache__/frontend_client.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..cafe8a14093cf89d080c4f1b6671136c1bc6e871 Binary files /dev/null and b/osm_ee/__pycache__/frontend_client.cpython-36.pyc differ diff --git a/osm_ee/__pycache__/frontend_grpc.cpython-36.pyc b/osm_ee/__pycache__/frontend_grpc.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c73803e1d3384aae7394153a4acd2b475f5b7730 Binary files /dev/null and b/osm_ee/__pycache__/frontend_grpc.cpython-36.pyc differ diff --git a/osm_ee/__pycache__/frontend_pb2.cpython-36.pyc b/osm_ee/__pycache__/frontend_pb2.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0424a29c566cb0e3c5bfae8cb832a583f8d8e660 Binary files /dev/null and b/osm_ee/__pycache__/frontend_pb2.cpython-36.pyc differ diff --git a/osm_ee/__pycache__/frontend_server.cpython-36.pyc b/osm_ee/__pycache__/frontend_server.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d3622cb1d48088846f0a131b6602b57b6c374ace Binary files /dev/null and b/osm_ee/__pycache__/frontend_server.cpython-36.pyc differ diff --git a/osm_ee/base_ee.py b/osm_ee/base_ee.py new file mode 100644 index 0000000000000000000000000000000000000000..6728c4939be54d8c72e4e754fcc898cec5a06c8e --- /dev/null +++ b/osm_ee/base_ee.py @@ -0,0 +1,105 @@ +## +# Copyright 2019 Telefonica Investigacion y Desarrollo, S.A.U. +# This file is part of OSM +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# For those usages not covered by the Apache License, Version 2.0 please +# contact with: nfvlabs@tid.es +## + +import asyncio +import logging +import yaml +import os + +from osm_ee.vnf.vnf_ee import VnfEE + + +class BaseEE: + + RETURN_STATUS_LIST = ["OK", "PROCESSING", "ERROR"] + CONFIG_FILE = "/app/storage/config.yaml" + SSH_KEY_FILE = "~/.ssh/id_rsa.pub" + HEALTH_CHECK_ACTION = "health-check" + + def __init__(self): + self.logger = logging.getLogger('osm_ee.base') + + # Check if configuration is stored and load it + if os.path.exists(self.CONFIG_FILE): + with open(self.CONFIG_FILE, 'r') as file: + self.config_params = yaml.load(file, Loader=yaml.FullLoader) + self.logger.debug("Load existing config from file: {}".format(self.config_params)) + else: + self.config_params = {} + + self.vnf_ee = VnfEE(self.config_params) + + async def get_ssh_key(self): + self.logger.debug("Obtain ssh key") + filename = os.path.expanduser(self.SSH_KEY_FILE) + with open(filename) as reader: + ssh_key = reader.read() + return ssh_key + + async def run_action(self, id, name, params): + self.logger.debug("Execute action id: {}, name: {}, params: {}".format(id, name, params)) + + try: + # Health-check + if name == self.HEALTH_CHECK_ACTION: + yield "OK", "Health-check ok" + else: + + # Obtain dynamically code to be executed + method = getattr(self.vnf_ee, name) + + # Convert params from yaml format + action_params = yaml.safe_load(params) + + if name == "config": + self.logger.debug("Store config info in file: {}".format(self.CONFIG_FILE)) + self.config_params.update(action_params) + with open(self.CONFIG_FILE, 'w') as file: + config = yaml.dump(self.config_params, file) + + async for return_status, detailed_message in method(id, action_params): + if return_status not in self.RETURN_STATUS_LIST: + yield "ERROR", "Invalid return status" + else: + yield return_status, str(detailed_message) + except AttributeError as e: + error_msg = "Action name: {} not implemented".format(name) + self.logger.error(error_msg) + yield "ERROR", error_msg + except Exception as e: + self.logger.error("Error executing action id, name: {},{}: {}".format(id, name, str(e)), exc_info=True) + yield "ERROR", str(e) + + +if __name__ == '__main__': + logging.basicConfig(level=logging.DEBUG) + + loop = asyncio.get_event_loop() + try: + ee = BaseEE() + id = "test1" + name = "touch2" + params = {"file_path": "/var/tmp/testfile1.txt"} + action = asyncio.ensure_future(ee.run_action(id, name, params)) + loop.run_until_complete(action) + finally: + loop.close() diff --git a/osm_ee/exceptions.py b/osm_ee/exceptions.py new file mode 100644 index 0000000000000000000000000000000000000000..4829197a27d3e4015a4a3571b71aca6096629405 --- /dev/null +++ b/osm_ee/exceptions.py @@ -0,0 +1,27 @@ +## +# Copyright 2019 Telefonica Investigacion y Desarrollo, S.A.U. +# This file is part of OSM +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# For those usages not covered by the Apache License, Version 2.0 please +# contact with: nfvlabs@tid.es +## + +class ExecEnvException(Exception): + """Exception thrown by the EE if the actions can't be invoked or there is any generic error""" + +class VnfException(Exception): + """Exception thrown by the Vnf EE code in case of error""" \ No newline at end of file diff --git a/osm_ee/frontend.proto b/osm_ee/frontend.proto new file mode 100644 index 0000000000000000000000000000000000000000..36d01585fb36e9b588ce7380e81fefdec245ca48 --- /dev/null +++ b/osm_ee/frontend.proto @@ -0,0 +1,39 @@ +syntax = "proto3"; + +option java_multiple_files = true; +option java_package = "com.etsi.osm.lcm.osm_ee"; +option java_outer_classname = "GrpcExecutor"; +option objc_class_prefix = "OEE"; + +package osm_ee; + +service FrontendExecutor { + + // Execute action leaving open a channel for notifications + rpc RunPrimitive (PrimitiveRequest) returns (stream PrimitiveReply) {} + + // Sends a greeting + rpc GetSshKey (SshKeyRequest) returns (SshKeyReply) {} +} + +// Request parameters +message PrimitiveRequest { + string id=1; + string name=2; + string params=3; +} + +message PrimitiveReply { + string status=1; + string detailed_message=2; +} + +// The request message containing the user's name. +message SshKeyRequest { + // empty +} + +// The response message containing the greetings +message SshKeyReply { + string message = 1; +} diff --git a/osm_ee/frontend_client.py b/osm_ee/frontend_client.py new file mode 100644 index 0000000000000000000000000000000000000000..0c345807616b042c3304e2024aaa994ce61ee537 --- /dev/null +++ b/osm_ee/frontend_client.py @@ -0,0 +1,85 @@ +## +# Copyright 2019 Telefonica Investigacion y Desarrollo, S.A.U. +# This file is part of OSM +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# For those usages not covered by the Apache License, Version 2.0 please +# contact with: nfvlabs@tid.es +## + +import sys +import yaml +import asyncio +import uuid +import traceback +import os +import socket + +from grpclib.client import Channel + +from osm_ee.frontend_pb2 import PrimitiveRequest, PrimitiveReply +from osm_ee.frontend_pb2 import SshKeyRequest, SshKeyReply +from osm_ee.frontend_grpc import FrontendExecutorStub + + +async def frontend_client(host_name, port, primitive_name, params): + + ip_addr = socket.gethostbyname(host_name) + channel = Channel(ip_addr, port) + try: + stub = FrontendExecutorStub(channel) + + if (primitive_name == "get_ssh_key"): + print("Get ssh key") + reply: SshKeyReply = await stub.GetSshKey(SshKeyRequest()) + print(reply.message) + else: + async with stub.RunPrimitive.open() as stream: + primitive_id = str(uuid.uuid1()) + print("Execute primitive {}, params: {}".format(primitive_name, params)) + await stream.send_message( + PrimitiveRequest(id=primitive_id, name=primitive_name, params=yaml.dump(params)), end=True) + async for reply in stream: + print(reply) + #replies = [reply async for reply in stream] + #print(replies) + except Exception as e: + print("Error executing primitive {}: {}".format(primitive_name, str(e))) + #print(traceback.format_exc()) + finally: + channel.close() + + +if __name__ == '__main__': + + args = sys.argv[1:] + if (len(args) < 1): + print("Usage: host port primitive_name params") + else: + host_name = args[0] + port = args[1] + primitive_name = args[2] + arg_params = args[3] if len(args) >= 4 else "" + print(primitive_name) + print(arg_params) + params = yaml.safe_load(arg_params) + + loop = asyncio.get_event_loop() + try: + task = asyncio.ensure_future(frontend_client(host_name, port, primitive_name, params)) + loop.run_until_complete(task) + finally: + loop.close() diff --git a/osm_ee/frontend_grpc.py b/osm_ee/frontend_grpc.py new file mode 100644 index 0000000000000000000000000000000000000000..b2b95d395c792b08994b86471e518e56fa50e6c5 --- /dev/null +++ b/osm_ee/frontend_grpc.py @@ -0,0 +1,56 @@ +# Generated by the Protocol Buffers compiler. DO NOT EDIT! +# source: osm_ee/frontend.proto +# plugin: grpclib.plugin.main +import abc +import typing + +import grpclib.const +import grpclib.client +if typing.TYPE_CHECKING: + import grpclib.server + +import osm_ee.frontend_pb2 + + +class FrontendExecutorBase(abc.ABC): + + @abc.abstractmethod + async def RunPrimitive(self, stream: 'grpclib.server.Stream[osm_ee.frontend_pb2.PrimitiveRequest, osm_ee.frontend_pb2.PrimitiveReply]') -> None: + pass + + @abc.abstractmethod + async def GetSshKey(self, stream: 'grpclib.server.Stream[osm_ee.frontend_pb2.SshKeyRequest, osm_ee.frontend_pb2.SshKeyReply]') -> None: + pass + + def __mapping__(self) -> typing.Dict[str, grpclib.const.Handler]: + return { + '/osm_ee.FrontendExecutor/RunPrimitive': grpclib.const.Handler( + self.RunPrimitive, + grpclib.const.Cardinality.UNARY_STREAM, + osm_ee.frontend_pb2.PrimitiveRequest, + osm_ee.frontend_pb2.PrimitiveReply, + ), + '/osm_ee.FrontendExecutor/GetSshKey': grpclib.const.Handler( + self.GetSshKey, + grpclib.const.Cardinality.UNARY_UNARY, + osm_ee.frontend_pb2.SshKeyRequest, + osm_ee.frontend_pb2.SshKeyReply, + ), + } + + +class FrontendExecutorStub: + + def __init__(self, channel: grpclib.client.Channel) -> None: + self.RunPrimitive = grpclib.client.UnaryStreamMethod( + channel, + '/osm_ee.FrontendExecutor/RunPrimitive', + osm_ee.frontend_pb2.PrimitiveRequest, + osm_ee.frontend_pb2.PrimitiveReply, + ) + self.GetSshKey = grpclib.client.UnaryUnaryMethod( + channel, + '/osm_ee.FrontendExecutor/GetSshKey', + osm_ee.frontend_pb2.SshKeyRequest, + osm_ee.frontend_pb2.SshKeyReply, + ) diff --git a/osm_ee/frontend_pb2.py b/osm_ee/frontend_pb2.py new file mode 100644 index 0000000000000000000000000000000000000000..8cdc485a172182cf4b378aab6ebc9f32a5f7a5b0 --- /dev/null +++ b/osm_ee/frontend_pb2.py @@ -0,0 +1,233 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: osm_ee/frontend.proto + +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from google.protobuf import reflection as _reflection +from google.protobuf import symbol_database as _symbol_database +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + + + +DESCRIPTOR = _descriptor.FileDescriptor( + name='osm_ee/frontend.proto', + package='osm_ee', + syntax='proto3', + serialized_options=b'\n\027com.etsi.osm.lcm.osm_eeB\014GrpcExecutorP\001\242\002\003OEE', + serialized_pb=b'\n\x15osm_ee/frontend.proto\x12\x06osm_ee\"<\n\x10PrimitiveRequest\x12\n\n\x02id\x18\x01 \x01(\t\x12\x0c\n\x04name\x18\x02 \x01(\t\x12\x0e\n\x06params\x18\x03 \x01(\t\":\n\x0ePrimitiveReply\x12\x0e\n\x06status\x18\x01 \x01(\t\x12\x18\n\x10\x64\x65tailed_message\x18\x02 \x01(\t\"\x0f\n\rSshKeyRequest\"\x1e\n\x0bSshKeyReply\x12\x0f\n\x07message\x18\x01 \x01(\t2\x93\x01\n\x10\x46rontendExecutor\x12\x44\n\x0cRunPrimitive\x12\x18.osm_ee.PrimitiveRequest\x1a\x16.osm_ee.PrimitiveReply\"\x00\x30\x01\x12\x39\n\tGetSshKey\x12\x15.osm_ee.SshKeyRequest\x1a\x13.osm_ee.SshKeyReply\"\x00\x42/\n\x17\x63om.etsi.osm.lcm.osm_eeB\x0cGrpcExecutorP\x01\xa2\x02\x03OEEb\x06proto3' +) + + + + +_PRIMITIVEREQUEST = _descriptor.Descriptor( + name='PrimitiveRequest', + full_name='osm_ee.PrimitiveRequest', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='id', full_name='osm_ee.PrimitiveRequest.id', index=0, + number=1, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='name', full_name='osm_ee.PrimitiveRequest.name', index=1, + number=2, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='params', full_name='osm_ee.PrimitiveRequest.params', index=2, + number=3, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=33, + serialized_end=93, +) + + +_PRIMITIVEREPLY = _descriptor.Descriptor( + name='PrimitiveReply', + full_name='osm_ee.PrimitiveReply', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='status', full_name='osm_ee.PrimitiveReply.status', index=0, + number=1, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='detailed_message', full_name='osm_ee.PrimitiveReply.detailed_message', index=1, + number=2, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=95, + serialized_end=153, +) + + +_SSHKEYREQUEST = _descriptor.Descriptor( + name='SshKeyRequest', + full_name='osm_ee.SshKeyRequest', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=155, + serialized_end=170, +) + + +_SSHKEYREPLY = _descriptor.Descriptor( + name='SshKeyReply', + full_name='osm_ee.SshKeyReply', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='message', full_name='osm_ee.SshKeyReply.message', index=0, + number=1, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=172, + serialized_end=202, +) + +DESCRIPTOR.message_types_by_name['PrimitiveRequest'] = _PRIMITIVEREQUEST +DESCRIPTOR.message_types_by_name['PrimitiveReply'] = _PRIMITIVEREPLY +DESCRIPTOR.message_types_by_name['SshKeyRequest'] = _SSHKEYREQUEST +DESCRIPTOR.message_types_by_name['SshKeyReply'] = _SSHKEYREPLY +_sym_db.RegisterFileDescriptor(DESCRIPTOR) + +PrimitiveRequest = _reflection.GeneratedProtocolMessageType('PrimitiveRequest', (_message.Message,), { + 'DESCRIPTOR' : _PRIMITIVEREQUEST, + '__module__' : 'osm_ee.frontend_pb2' + # @@protoc_insertion_point(class_scope:osm_ee.PrimitiveRequest) + }) +_sym_db.RegisterMessage(PrimitiveRequest) + +PrimitiveReply = _reflection.GeneratedProtocolMessageType('PrimitiveReply', (_message.Message,), { + 'DESCRIPTOR' : _PRIMITIVEREPLY, + '__module__' : 'osm_ee.frontend_pb2' + # @@protoc_insertion_point(class_scope:osm_ee.PrimitiveReply) + }) +_sym_db.RegisterMessage(PrimitiveReply) + +SshKeyRequest = _reflection.GeneratedProtocolMessageType('SshKeyRequest', (_message.Message,), { + 'DESCRIPTOR' : _SSHKEYREQUEST, + '__module__' : 'osm_ee.frontend_pb2' + # @@protoc_insertion_point(class_scope:osm_ee.SshKeyRequest) + }) +_sym_db.RegisterMessage(SshKeyRequest) + +SshKeyReply = _reflection.GeneratedProtocolMessageType('SshKeyReply', (_message.Message,), { + 'DESCRIPTOR' : _SSHKEYREPLY, + '__module__' : 'osm_ee.frontend_pb2' + # @@protoc_insertion_point(class_scope:osm_ee.SshKeyReply) + }) +_sym_db.RegisterMessage(SshKeyReply) + + +DESCRIPTOR._options = None + +_FRONTENDEXECUTOR = _descriptor.ServiceDescriptor( + name='FrontendExecutor', + full_name='osm_ee.FrontendExecutor', + file=DESCRIPTOR, + index=0, + serialized_options=None, + serialized_start=205, + serialized_end=352, + methods=[ + _descriptor.MethodDescriptor( + name='RunPrimitive', + full_name='osm_ee.FrontendExecutor.RunPrimitive', + index=0, + containing_service=None, + input_type=_PRIMITIVEREQUEST, + output_type=_PRIMITIVEREPLY, + serialized_options=None, + ), + _descriptor.MethodDescriptor( + name='GetSshKey', + full_name='osm_ee.FrontendExecutor.GetSshKey', + index=1, + containing_service=None, + input_type=_SSHKEYREQUEST, + output_type=_SSHKEYREPLY, + serialized_options=None, + ), +]) +_sym_db.RegisterServiceDescriptor(_FRONTENDEXECUTOR) + +DESCRIPTOR.services_by_name['FrontendExecutor'] = _FRONTENDEXECUTOR + +# @@protoc_insertion_point(module_scope) diff --git a/osm_ee/frontend_server.py b/osm_ee/frontend_server.py new file mode 100644 index 0000000000000000000000000000000000000000..079503d1e9e50f6408e83df933f6e566810ec2b9 --- /dev/null +++ b/osm_ee/frontend_server.py @@ -0,0 +1,89 @@ +## +# Copyright 2019 Telefonica Investigacion y Desarrollo, S.A.U. +# This file is part of OSM +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# For those usages not covered by the Apache License, Version 2.0 please +# contact with: nfvlabs@tid.es +## + +import asyncio +import logging +import os + +from grpclib.utils import graceful_exit +from grpclib.server import Server, Stream + +from osm_ee.frontend_grpc import FrontendExecutorBase +from osm_ee.frontend_pb2 import PrimitiveRequest, PrimitiveReply +from osm_ee.frontend_pb2 import SshKeyRequest, SshKeyReply + +from osm_ee.base_ee import BaseEE +import osm_ee.util.util_ee as util_ee + + +class FrontendExecutor(FrontendExecutorBase): + + def __init__(self): + self.logger = logging.getLogger('osm_ee.frontend_server') + self.base_ee = BaseEE() + + async def RunPrimitive(self, stream: Stream[PrimitiveRequest, PrimitiveReply]) -> None: + request = await stream.recv_message() + try: + self.logger.debug(f'Run primitive: id {request.id}, name: {request.name}, params: {request.params}') + async for status, detailed_message in self.base_ee.run_action(request.id, request.name, request.params): + self.logger.debug(f'Send response {status}, {detailed_message}') + await stream.send_message( + PrimitiveReply(status=status, detailed_message=detailed_message)) + except Exception as e: + self.logger.debug(f'Error executing primitive: id {request.id}, name: {request.name}, error_msg: {str(e)}') + await stream.send_message( + PrimitiveReply(status="ERROR", detailed_message=str(e))) + + async def GetSshKey(self, stream: Stream[SshKeyRequest, SshKeyReply]) -> None: + request = await stream.recv_message() + assert request is not None + message = await self.base_ee.get_ssh_key() + await stream.send_message(SshKeyReply(message=message)) + + +async def main(*, host: str = '0.0.0.0', port: int = 50051) -> None: + logging.basicConfig() + logger = logging.getLogger('osm_ee') + logger.setLevel(logging.DEBUG) + + # Generate ssh key + file_dir = os.path.expanduser("~/.ssh/id_rsa") + command = "ssh-keygen -q -t rsa -N '' -f {}".format(file_dir) + return_code, stdout, stderr = await util_ee.local_async_exec(command) + logger.debug("Generated ssh_key, return_code: {}".format(return_code)) + + # Start server + server = Server([FrontendExecutor()]) + with graceful_exit([server]): + await server.start(host, port) + logging.getLogger('osm_ee.frontend_server').debug(f'Serving on {host}:{port}') + await server.wait_closed() + + +if __name__ == '__main__': + loop = asyncio.get_event_loop() + try: + main_task = asyncio.ensure_future(main()) + loop.run_until_complete(main_task) + finally: + loop.close() diff --git a/osm_ee/scripts/ee_start.sh b/osm_ee/scripts/ee_start.sh new file mode 100755 index 0000000000000000000000000000000000000000..0364dc407c96660cb7ba25a9bf75f6f249f5abf4 --- /dev/null +++ b/osm_ee/scripts/ee_start.sh @@ -0,0 +1,30 @@ +#!/bin/bash +## +# Copyright 2015 Telefonica Investigacion y Desarrollo, S.A.U. +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +## + +# This script is intended for launching RO from a docker container. +# It waits for mysql server ready, normally running on a separate container, ... +# then it checks if database is present and creates it if needed. +# Finally it launches RO server. + +EE_PATH=/app/EE + +# Install vnf vendor additional required libraries +echo "Install additional libraries" +bash ${EE_PATH}/osm_ee/vnf/install.sh + +# Start frontend +echo "Starting frontend server" +python3 -m osm_ee.frontend_server diff --git a/osm_ee/util/__init__.py b/osm_ee/util/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/osm_ee/util/__pycache__/__init__.cpython-36.pyc b/osm_ee/util/__pycache__/__init__.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..138334b80a9424ece48476ad22c8761dc661684c Binary files /dev/null and b/osm_ee/util/__pycache__/__init__.cpython-36.pyc differ diff --git a/osm_ee/util/__pycache__/util_ansible.cpython-36.pyc b/osm_ee/util/__pycache__/util_ansible.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d6cbaeb1cf8b7393f8b1f290aae152024069db36 Binary files /dev/null and b/osm_ee/util/__pycache__/util_ansible.cpython-36.pyc differ diff --git a/osm_ee/util/__pycache__/util_ee.cpython-36.pyc b/osm_ee/util/__pycache__/util_ee.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2c6940c431b2d7dcd070c27b4cef7ae32262676d Binary files /dev/null and b/osm_ee/util/__pycache__/util_ee.cpython-36.pyc differ diff --git a/osm_ee/util/util_ansible.py b/osm_ee/util/util_ansible.py new file mode 100644 index 0000000000000000000000000000000000000000..8f27875d36bb06ecbabbd1da6ca9a004e38f6f06 --- /dev/null +++ b/osm_ee/util/util_ansible.py @@ -0,0 +1,46 @@ +## +# Copyright 2019 Telefonica Investigacion y Desarrollo, S.A.U. +# This file is part of OSM +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# For those usages not covered by the Apache License, Version 2.0 please +# contact with: nfvlabs@tid.es +## + +import logging +import json +from shlex import quote + +import osm_ee.util.util_ee as util_ee + +logger = logging.getLogger("osm_ee.util_ansible") + + +async def execute_playbook(playbook_name: str, inventory: str, extra_vars: dict, + ) -> (int, str): + + command = 'ansible-playbook --inventory={} --extra-vars {} {}'.format(quote(inventory), + quote(json.dumps(extra_vars)), + quote(playbook_name)) + + logger.debug("Command to be executed: {}".format(command)) + + return_code, stdout, stderr = await util_ee.local_async_exec(command) + logger.debug("Return code: {}".format(return_code)) + logger.debug("stdout: {}".format(stdout)) + logger.debug("stderr: {}".format(stderr)) + + return return_code, stdout, stderr diff --git a/osm_ee/util/util_ee.py b/osm_ee/util/util_ee.py new file mode 100644 index 0000000000000000000000000000000000000000..1d0a2325615d8b54986c946ae93a41afbd5f36b5 --- /dev/null +++ b/osm_ee/util/util_ee.py @@ -0,0 +1,60 @@ +## +# Copyright 2019 Telefonica Investigacion y Desarrollo, S.A.U. +# This file is part of OSM +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# For those usages not covered by the Apache License, Version 2.0 please +# contact with: nfvlabs@tid.es +## + +import logging +import asyncio +from shlex import split + +logger = logging.getLogger("osm_ee.util") + + +async def local_async_exec(command: str + ) -> (int, str, str): + """ + Executed a local command using asyncio. + TODO - do not know yet if return error code, and stdout and strerr or just one of them + """ + scommand = split(command) + + logger.debug("Execute local command: {}".format(command)) + process = await asyncio.create_subprocess_exec( + *scommand, + stdout=asyncio.subprocess.PIPE, + stderr=asyncio.subprocess.PIPE + ) + + # wait for command terminate + stdout, stderr = await process.communicate() + + return_code = process.returncode + logger.debug("Return code: {}".format(return_code)) + + output = "" + if stdout: + output = stdout.decode() + logger.debug("Output: {}".format(output)) + + if stderr: + out_err = stderr.decode() + logger.debug("Stderr: {}".format(out_err)) + + return return_code, stdout, stderr diff --git a/osm_ee/vnf/__init__.py b/osm_ee/vnf/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/osm_ee/vnf/__pycache__/__init__.cpython-36.pyc b/osm_ee/vnf/__pycache__/__init__.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..70afb9ec13739c41c20094e37b47a1bb98305d86 Binary files /dev/null and b/osm_ee/vnf/__pycache__/__init__.cpython-36.pyc differ diff --git a/osm_ee/vnf/__pycache__/vnf_ee.cpython-36.pyc b/osm_ee/vnf/__pycache__/vnf_ee.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..27ab0d3ebf7c7685100d5da22fbab4ce8ea2ad9b Binary files /dev/null and b/osm_ee/vnf/__pycache__/vnf_ee.cpython-36.pyc differ diff --git a/osm_ee/vnf/install.sh b/osm_ee/vnf/install.sh new file mode 100644 index 0000000000000000000000000000000000000000..e0e56ffcd56a4a53d951b254af23ef6c6c385061 --- /dev/null +++ b/osm_ee/vnf/install.sh @@ -0,0 +1,22 @@ +#!/bin/bash +## +# Copyright 2015 Telefonica Investigacion y Desarrollo, S.A.U. +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +## + +# This script is intended for launching RO from a docker container. +# It waits for mysql server ready, normally running on a separate container, ... +# then it checks if database is present and creates it if needed. +# Finally it launches RO server. + +echo "Sample install.sh " \ No newline at end of file diff --git a/osm_ee/vnf/vnf_ee.py b/osm_ee/vnf/vnf_ee.py new file mode 100644 index 0000000000000000000000000000000000000000..10c2e33a14bb366553e4ef5cb3d2ec613780a094 --- /dev/null +++ b/osm_ee/vnf/vnf_ee.py @@ -0,0 +1,54 @@ +## +# Copyright 2019 Telefonica Investigacion y Desarrollo, S.A.U. +# This file is part of OSM +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# For those usages not covered by the Apache License, Version 2.0 please +# contact with: nfvlabs@tid.es +## + +import asyncio +import logging + +from osm_ee.exceptions import VnfException + + +class VnfEE: + + def __init__(self, config_params): + self.logger = logging.getLogger('osm_ee.vnf') + self.config_params = config_params + + async def config(self, id, params): + self.logger.debug("Execute action config params: {}".format(params)) + # Config action is special, params are merged with previous config calls + self.config_params.update(params) + yield "OK", "Configured" + + async def sleep(self, id, params): + self.logger.debug("Execute action sleep, params: {}".format(params)) + + for i in range(3): + await asyncio.sleep(5) + self.logger.debug("Temporal result return, params: {}".format(params)) + yield "PROCESSING", f"Processing {i} action id {id}" + yield "OK", f"Processed action id {id}" + + @staticmethod + def _check_required_params(params, required_params): + for required_param in required_params: + if required_param not in params: + raise VnfException("Missing required param: {}".format(required_param)) diff --git a/setup.py b/setup.py new file mode 100644 index 0000000000000000000000000000000000000000..ab68a849d71d1188f5fe2d9ea7e67121cc6a7899 --- /dev/null +++ b/setup.py @@ -0,0 +1,59 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +# Copyright 2018 Telefonica S.A. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from setuptools import setup + +_name = "osm_ee" + +_description = 'OSM Resource Orchestrator' +_author = 'ETSI OSM' +_author_email = 'illoret@indra.es' +_maintainer = 'alfonso.tiernosepulveda' +_maintainer_email = 'alfonso.tiernosepulveda@telefonica.com' +_license = 'Apache 2.0' +_url = 'TOBEDEFINED' +_requirements = [ + # Libraries needed by the code defined by osm + "PyYAML", + "grpcio-tools", + "grpclib", + "protobuf", + + # Libraries defined by the vnf code, they should be in an external file + #"asyncssh", +] + +setup( + name=_name, + #version_command=('0.1'), # TODO - replace for a git command + version='1.0', + description=_description, + long_description=open('README.rst').read(), + author=_author, + author_email=_author_email, + maintainer=_maintainer, + maintainer_email=_maintainer_email, + url=_url, + license=_license, + packages=[_name], + package_dir={_name: _name}, + + install_requires=_requirements, + include_package_data=True, + setup_requires=['setuptools-version-command'], +) \ No newline at end of file