This commit cleans up the python directory to ensure the adapters
and the cli runs properly.
Change-Id: Ic68a3ecd1f16a5af44296e3c020c808b185f4c18
diff --git a/python/adapters/Makefile b/python/Makefile
similarity index 97%
rename from python/adapters/Makefile
rename to python/Makefile
index 2531985..b19472f 100644
--- a/python/adapters/Makefile
+++ b/python/Makefile
@@ -88,7 +88,7 @@
FETCH_IMAGE_LIST = $(shell echo $(FETCH_BUILD_IMAGE_LIST) $(FETCH_COMPOSE_IMAGE_LIST) $(FETCH_K8S_IMAGE_LIST) | tr ' ' '\n' | sort -u)
-.PHONY: $(DIRS) $(DIRS_CLEAN) $(DIRS_FLAKE8) flake8 base ponsim_olt ponsim_onu protos kafka common start stop tag push pull
+.PHONY: $(DIRS) $(DIRS_CLEAN) $(DIRS_FLAKE8) flake8 base ponsim_olt ponsim_onu protos cli kafka common start stop tag push pull
# This should to be the first and default target in this Makefile
help:
@@ -154,6 +154,9 @@
adapter_ponsim_onu:
docker build $(DOCKER_BUILD_ARGS) -t ${REGISTRY}${REPOSITORY}voltha-adapter-ponsim-onu:${TAG} -f docker/Dockerfile.adapter_ponsim_onu .
+cli:
+ docker build $(DOCKER_BUILD_ARGS) -t ${REGISTRY}${REPOSITORY}voltha-cli:${TAG} -f docker/Dockerfile.cli .
+
tag: $(patsubst %,%.tag,$(DOCKER_IMAGE_LIST))
push: tag $(patsubst %,%.push,$(DOCKER_IMAGE_LIST))
diff --git a/python/adapters/protos/__init__.py b/python/__init__.py
similarity index 100%
copy from python/adapters/protos/__init__.py
copy to python/__init__.py
diff --git a/python/adapters/common/__init__.py b/python/adapters/common/__init__.py
index b0fb0b2..58aca1e 100644
--- a/python/adapters/common/__init__.py
+++ b/python/adapters/common/__init__.py
@@ -1,10 +1,10 @@
-# Copyright 2017-present Open Networking Foundation
+# Copyright 2018 the original author or authors.
#
# 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
+# 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,
diff --git a/python/adapters/common/event_bus.py b/python/adapters/common/event_bus.py
deleted file mode 100644
index e717c16..0000000
--- a/python/adapters/common/event_bus.py
+++ /dev/null
@@ -1,194 +0,0 @@
-#
-# Copyright 2017 the original author or authors.
-#
-# 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.
-#
-
-"""
-A simple internal pub/sub event bus with topics and filter-based registration.
-"""
-import re
-
-import structlog
-
-
-log = structlog.get_logger()
-
-
-class _Subscription(object):
-
- __slots__ = ('bus', 'predicate', 'callback', 'topic')
- def __init__(self, bus, predicate, callback, topic=None):
- self.bus = bus
- self.predicate = predicate
- self.callback = callback
- self.topic = topic
-
-
-class EventBus(object):
-
- def __init__(self):
- self.subscriptions = {} # topic -> list of _Subscription objects
- # topic None holds regexp based topic subs.
- self.subs_topic_map = {} # to aid fast lookup when unsubscribing
-
- def list_subscribers(self, topic=None):
- if topic is None:
- return sum(self.subscriptions.itervalues(), [])
- else:
- if topic in self.subscriptions:
- return self.subscriptions[topic]
- else:
- return []
-
- @staticmethod
- def _get_topic_key(topic):
- if isinstance(topic, str):
- return topic
- elif hasattr(topic, 'match'):
- return None
- else:
- raise AttributeError('topic not a string nor a compiled regex')
-
- def subscribe(self, topic, callback, predicate=None):
- """
- Subscribe to given topic with predicate and register the callback
- :param topic: String topic (explicit) or regexp based topic filter.
- :param callback: Callback method with signature def func(topic, msg)
- :param predicate: Optional method/function signature def predicate(msg)
- :return: Subscription object which can be used to unsubscribe
- """
- subscription = _Subscription(self, predicate, callback, topic)
- topic_key = self._get_topic_key(topic)
- self.subscriptions.setdefault(topic_key, []).append(subscription)
- self.subs_topic_map[subscription] = topic_key
- return subscription
-
- def unsubscribe(self, subscription):
- """
- Remove given subscription
- :param subscription: subscription object as was returned by subscribe
- :return: None
- """
- topic_key = self.subs_topic_map[subscription]
- self.subscriptions[topic_key].remove(subscription)
-
- def publish(self, topic, msg):
- """
- Publish given message to all subscribers registered with topic taking
- the predicate functions into account.
- :param topic: String topic
- :param msg: Arbitrary python data as message
- :return: None
- """
- from copy import copy
-
- def passes(msg, predicate):
- try:
- return predicate(msg)
- except Exception, e:
- return False # failed predicate function treated as no match
-
- # lookup subscribers with explicit topic subscriptions
- subscribers = self.subscriptions.get(topic, [])
-
- # add matching regexp topic subscribers
- subscribers.extend(s for s in self.subscriptions.get(None, [])
- if s.topic.match(topic))
-
- # iterate over a shallow-copy of subscribers
- for candidate in copy(subscribers):
- predicate = candidate.predicate
- if predicate is None or passes(msg, predicate):
- try:
- candidate.callback(topic, msg)
- except Exception, e:
- log.exception('callback-failed', e=repr(e), topic=topic)
-
-
-
-default_bus = EventBus()
-
-
-class EventBusClient(object):
- """
- Primary interface to the EventBus. Usage:
-
- Publish:
- >>> events = EventBusClient()
- >>> msg = dict(a=1, b='foo')
- >>> events.publish('a.topic', msg)
-
- Subscribe to get all messages on specific topic:
- >>> def got_event(topic, msg):
- >>> print topic, ':', msg
- >>> events = EventBusClient()
- >>> events.subscribe('a.topic', got_event)
-
- Subscribe to get messages matching predicate on specific topic:
- >>> def got_event(topic, msg):
- >>> print topic, ':', msg
- >>> events = EventBusClient()
- >>> events.subscribe('a.topic', got_event, lambda msg: msg.len() < 100)
-
- Use a DeferredQueue to buffer incoming messages
- >>> queue = DeferredQueue()
- >>> events = EventBusClient()
- >>> events.subscribe('a.topic', lambda _, msg: queue.put(msg))
-
- """
- def __init__(self, bus=None):
- """
- Obtain a client interface for the pub/sub event bus.
- :param bus: An optional specific event bus. Inteded for mainly test
- use. If not provided, the process default bus will be used, which is
- the preferred use (a process shall not need more than one bus).
- """
- self.bus = bus or default_bus
-
- def publish(self, topic, msg):
- """
- Publish given msg to given topic.
- :param topic: String topic
- :param msg: Arbitrary python data as message
- :return: None
- """
- self.bus.publish(topic, msg)
-
- def subscribe(self, topic, callback, predicate=None):
- """
- Subscribe to given topic with predicate and register the callback
- :param topic: String topic (explicit) or regexp based topic filter.
- :param callback: Callback method with signature def func(topic, msg)
- :param predicate: Optional method/function with signature
- def predicate(msg)
- :return: Subscription object which can be used to unsubscribe
- """
- return self.bus.subscribe(topic, callback, predicate)
-
- def unsubscribe(self, subscription):
- """
- Remove given subscription
- :param subscription: subscription object as was returned by subscribe
- :return: None
- """
- return self.bus.unsubscribe(subscription)
-
- def list_subscribers(self, topic=None):
- """
- Return list of subscribers. If topci is provided, it is filtered for
- those subscribing to the topic.
- :param topic: Optional topic
- :return: List of subscriptions
- """
- return self.bus.list_subscribers(topic)
diff --git a/python/adapters/common/frameio/frameio.py b/python/adapters/common/frameio/frameio.py
index 2f68ef8..0657257 100644
--- a/python/adapters/common/frameio/frameio.py
+++ b/python/adapters/common/frameio/frameio.py
@@ -40,10 +40,10 @@
from twisted.internet import reactor
from zope.interface import implementer
-from adapters.common.utils.registry import IComponent
+from python.common.utils.registry import IComponent
if sys.platform.startswith('linux'):
- from adapters.common.frameio.third_party.oftest import afpacket, netutils
+ from third_party.oftest import afpacket, netutils
elif sys.platform == 'darwin':
from scapy.arch import pcapdnet, BIOCIMMEDIATE, dnet
diff --git a/python/common/kvstore/__init__.py b/python/adapters/common/kvstore/__init__.py
similarity index 100%
rename from python/common/kvstore/__init__.py
rename to python/adapters/common/kvstore/__init__.py
diff --git a/python/common/kvstore/consul_client.py b/python/adapters/common/kvstore/consul_client.py
similarity index 97%
rename from python/common/kvstore/consul_client.py
rename to python/adapters/common/kvstore/consul_client.py
index bc14759..789e797 100644
--- a/python/common/kvstore/consul_client.py
+++ b/python/adapters/common/kvstore/consul_client.py
@@ -12,9 +12,9 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-from common.kvstore.kv_client import DEFAULT_TIMEOUT, Event, KVClient, KVPair, RETRY_BACKOFF
-from common.utils.asleep import asleep
-from common.utils.deferred_utils import DeferredWithTimeout, TimeOutError
+from kv_client import DEFAULT_TIMEOUT, Event, KVClient, KVPair, RETRY_BACKOFF
+from python.common.utils.asleep import asleep
+from python.common.utils.deferred_utils import DeferredWithTimeout, TimeOutError
from consul import ConsulException
from consul.twisted import Consul
from structlog import get_logger
diff --git a/python/common/kvstore/etcd_client.py b/python/adapters/common/kvstore/etcd_client.py
similarity index 98%
rename from python/common/kvstore/etcd_client.py
rename to python/adapters/common/kvstore/etcd_client.py
index a958b71..e1850e7 100644
--- a/python/common/kvstore/etcd_client.py
+++ b/python/adapters/common/kvstore/etcd_client.py
@@ -23,7 +23,7 @@
#
################################################################################
-from common.kvstore.kv_client import DEFAULT_TIMEOUT, Event, KVClient, KVPair
+from kv_client import DEFAULT_TIMEOUT, Event, KVClient, KVPair
from structlog import get_logger
from twisted.internet import reactor
from twisted.internet.defer import inlineCallbacks, returnValue, Deferred
diff --git a/python/common/kvstore/kv_client.py b/python/adapters/common/kvstore/kv_client.py
similarity index 99%
rename from python/common/kvstore/kv_client.py
rename to python/adapters/common/kvstore/kv_client.py
index 69a6480..f6486f3 100644
--- a/python/common/kvstore/kv_client.py
+++ b/python/adapters/common/kvstore/kv_client.py
@@ -12,7 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-from common.utils.asleep import asleep
+from python.common.utils.asleep import asleep
from structlog import get_logger
from twisted.internet.defer import inlineCallbacks, returnValue
diff --git a/python/common/kvstore/kvstore.py b/python/adapters/common/kvstore/kvstore.py
similarity index 91%
rename from python/common/kvstore/kvstore.py
rename to python/adapters/common/kvstore/kvstore.py
index 662b34d..ed7f246 100644
--- a/python/common/kvstore/kvstore.py
+++ b/python/adapters/common/kvstore/kvstore.py
@@ -12,8 +12,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-from common.kvstore.consul_client import ConsulClient
-from common.kvstore.etcd_client import EtcdClient
+from consul_client import ConsulClient
+from etcd_client import EtcdClient
def create_kv_client(kv_store, host, port):
'''
diff --git a/python/adapters/common/manhole.py b/python/adapters/common/manhole.py
deleted file mode 100644
index c00c900..0000000
--- a/python/adapters/common/manhole.py
+++ /dev/null
@@ -1,129 +0,0 @@
-#
-# Copyright 2017 the original author or authors.
-#
-# 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.
-#
-import os
-import rlcompleter
-from pprint import pprint
-
-import structlog
-from twisted.conch import manhole_ssh
-from twisted.conch.manhole import ColoredManhole
-from twisted.conch.ssh import keys
-from twisted.cred.checkers import InMemoryUsernamePasswordDatabaseDontUse
-from twisted.cred.portal import Portal
-from twisted.internet import reactor
-
-log = structlog.get_logger()
-
-
-MANHOLE_SERVER_RSA_PRIVATE = './manhole_rsa_key'
-MANHOLE_SERVER_RSA_PUBLIC = './manhole_rsa_key.pub'
-
-
-def get_rsa_keys():
- if not (os.path.exists(MANHOLE_SERVER_RSA_PUBLIC) and \
- os.path.exists(MANHOLE_SERVER_RSA_PRIVATE)):
- # generate a RSA keypair
- log.info('generate-rsa-keypair')
- from Crypto.PublicKey import RSA
- rsa_key = RSA.generate(1024)
- public_key_str = rsa_key.publickey().exportKey(format='OpenSSH')
- private_key_str = rsa_key.exportKey()
-
- # save keys for next time
- file(MANHOLE_SERVER_RSA_PUBLIC, 'w+b').write(public_key_str)
- file(MANHOLE_SERVER_RSA_PRIVATE, 'w+b').write(private_key_str)
- log.debug('saved-rsa-keypair', public=MANHOLE_SERVER_RSA_PUBLIC,
- private=MANHOLE_SERVER_RSA_PRIVATE)
- else:
- public_key_str = file(MANHOLE_SERVER_RSA_PUBLIC).read()
- private_key_str = file(MANHOLE_SERVER_RSA_PRIVATE).read()
- return public_key_str, private_key_str
-
-
-class ManholeWithCompleter(ColoredManhole):
-
- def __init__(self, namespace):
- namespace['manhole'] = self
- super(ManholeWithCompleter, self).__init__(namespace)
- self.last_tab = None
- self.completer = rlcompleter.Completer(self.namespace)
-
- def handle_TAB(self):
- if self.last_tab != self.lineBuffer:
- self.last_tab = self.lineBuffer
- return
-
- buffer = ''.join(self.lineBuffer)
- completions = []
- maxlen = 3
- for c in xrange(1000):
- candidate = self.completer.complete(buffer, c)
- if not candidate:
- break
-
- if len(candidate) > maxlen:
- maxlen = len(candidate)
-
- completions.append(candidate)
-
- if len(completions) == 1:
- rest = completions[0][len(buffer):]
- self.terminal.write(rest)
- self.lineBufferIndex += len(rest)
- self.lineBuffer.extend(rest)
-
- elif len(completions):
- maxlen += 3
- numcols = self.width / maxlen
- self.terminal.nextLine()
- for idx, candidate in enumerate(completions):
- self.terminal.write('%%-%ss' % maxlen % candidate)
- if not ((idx + 1) % numcols):
- self.terminal.nextLine()
- self.terminal.nextLine()
- self.drawInputLine()
-
-
-class Manhole(object):
-
- def __init__(self, port, pws, **kw):
- kw.update(globals())
- kw['pp'] = pprint
-
- realm = manhole_ssh.TerminalRealm()
- manhole = ManholeWithCompleter(kw)
-
- def windowChanged(_, win_size):
- manhole.terminalSize(*reversed(win_size[:2]))
-
- realm.sessionFactory.windowChanged = windowChanged
- realm.chainedProtocolFactory.protocolFactory = lambda _: manhole
- portal = Portal(realm)
- portal.registerChecker(InMemoryUsernamePasswordDatabaseDontUse(**pws))
- factory = manhole_ssh.ConchFactory(portal)
- public_key_str, private_key_str = get_rsa_keys()
- factory.publicKeys = {
- 'ssh-rsa': keys.Key.fromString(public_key_str)
- }
- factory.privateKeys = {
- 'ssh-rsa': keys.Key.fromString(private_key_str)
- }
- reactor.listenTCP(port, factory, interface='localhost')
-
-
-if __name__ == '__main__':
- Manhole(12222, dict(admin='admin'))
- reactor.run()
diff --git a/python/common/pon_resource_manager/__init__.py b/python/adapters/common/pon_resource_manager/__init__.py
similarity index 100%
rename from python/common/pon_resource_manager/__init__.py
rename to python/adapters/common/pon_resource_manager/__init__.py
diff --git a/python/common/pon_resource_manager/resource_kv_store.py b/python/adapters/common/pon_resource_manager/resource_kv_store.py
similarity index 100%
rename from python/common/pon_resource_manager/resource_kv_store.py
rename to python/adapters/common/pon_resource_manager/resource_kv_store.py
diff --git a/python/common/pon_resource_manager/resource_manager.py b/python/adapters/common/pon_resource_manager/resource_manager.py
similarity index 100%
rename from python/common/pon_resource_manager/resource_manager.py
rename to python/adapters/common/pon_resource_manager/resource_manager.py
diff --git a/python/adapters/common/structlog_setup.py b/python/adapters/common/structlog_setup.py
deleted file mode 100644
index 3401977..0000000
--- a/python/adapters/common/structlog_setup.py
+++ /dev/null
@@ -1,134 +0,0 @@
-#
-# Copyright 2017 the original author or authors.
-#
-# 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.
-#
-
-"""Setting up proper logging for Voltha"""
-
-import logging
-import logging.config
-from collections import OrderedDict
-
-import structlog
-from structlog.stdlib import BoundLogger, INFO
-
-try:
- from thread import get_ident as _get_ident
-except ImportError:
- from dummy_thread import get_ident as _get_ident
-
-
-class StructuredLogRenderer(object):
- def __call__(self, logger, name, event_dict):
- # in order to keep structured log data in event_dict to be forwarded as
- # is, we need to pass it into the logger framework as the first
- # positional argument.
- args = (event_dict,)
- kwargs = {}
- return args, kwargs
-
-
-class PlainRenderedOrderedDict(OrderedDict):
- """Our special version of OrderedDict that renders into string as a dict,
- to make the log stream output cleaner.
- """
- def __repr__(self, _repr_running={}):
- 'od.__repr__() <==> repr(od)'
- call_key = id(self), _get_ident()
- if call_key in _repr_running:
- return '...'
- _repr_running[call_key] = 1
- try:
- if not self:
- return '{}'
- return '{%s}' % ", ".join("%s: %s" % (k, v)
- for k, v in self.items())
- finally:
- del _repr_running[call_key]
-
-
-def setup_logging(log_config, instance_id, verbosity_adjust=0):
- """
- Set up logging such that:
- - The primary logging entry method is structlog
- (see http://structlog.readthedocs.io/en/stable/index.html)
- - By default, the logging backend is Python standard lib logger
- """
-
- def add_exc_info_flag_for_exception(_, name, event_dict):
- if name == 'exception':
- event_dict['exc_info'] = True
- return event_dict
-
- def add_instance_id(_, __, event_dict):
- event_dict['instance_id'] = instance_id
- return event_dict
-
- # Configure standard logging
- logging.config.dictConfig(log_config)
- logging.root.level -= 10 * verbosity_adjust
-
- processors = [
- add_exc_info_flag_for_exception,
- structlog.processors.StackInfoRenderer(),
- structlog.processors.format_exc_info,
- add_instance_id,
- StructuredLogRenderer(),
- ]
- structlog.configure(logger_factory=structlog.stdlib.LoggerFactory(),
- context_class=PlainRenderedOrderedDict,
- wrapper_class=BoundLogger,
- processors=processors)
-
- # Mark first line of log
- log = structlog.get_logger()
- log.info("first-line")
- return log
-
-
-def update_logging(instance_id, vcore_id):
- """
- Add the vcore id to the structured logger
- :param vcore_id: The assigned vcore id
- :return: structure logger
- """
- def add_exc_info_flag_for_exception(_, name, event_dict):
- if name == 'exception':
- event_dict['exc_info'] = True
- return event_dict
-
- def add_instance_id(_, __, event_dict):
- if instance_id is not None:
- event_dict['instance_id'] = instance_id
- return event_dict
-
- def add_vcore_id(_, __, event_dict):
- if vcore_id is not None:
- event_dict['vcore_id'] = vcore_id
- return event_dict
-
- processors = [
- add_exc_info_flag_for_exception,
- structlog.processors.StackInfoRenderer(),
- structlog.processors.format_exc_info,
- add_instance_id,
- add_vcore_id,
- StructuredLogRenderer(),
- ]
- structlog.configure(processors=processors)
-
- # Mark first line of log
- log = structlog.get_logger()
- log.info("updated-logger")
- return log
diff --git a/python/adapters/common/utils/__init__.py b/python/adapters/common/utils/__init__.py
deleted file mode 100644
index b0fb0b2..0000000
--- a/python/adapters/common/utils/__init__.py
+++ /dev/null
@@ -1,13 +0,0 @@
-# Copyright 2017-present Open Networking Foundation
-#
-# 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.
diff --git a/python/adapters/common/utils/asleep.py b/python/adapters/common/utils/asleep.py
deleted file mode 100644
index 10d1ce3..0000000
--- a/python/adapters/common/utils/asleep.py
+++ /dev/null
@@ -1,31 +0,0 @@
-#
-# Copyright 2017 the original author or authors.
-#
-# 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.
-#
-
-""" Async sleep (asleep) method and other twisted goodies """
-
-from twisted.internet import reactor
-from twisted.internet.defer import Deferred
-
-
-def asleep(dt):
- """
- Async (event driven) wait for given time period (in seconds)
- :param dt: Delay in seconds
- :return: Deferred to be fired with value None when time expires.
- """
- d = Deferred()
- reactor.callLater(dt, lambda: d.callback(None))
- return d
diff --git a/python/adapters/common/utils/consulhelpers.py b/python/adapters/common/utils/consulhelpers.py
deleted file mode 100644
index 6060ba3..0000000
--- a/python/adapters/common/utils/consulhelpers.py
+++ /dev/null
@@ -1,178 +0,0 @@
-#
-# Copyright 2017 the original author or authors.
-#
-# 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.
-#
-
-"""
-Some consul related convenience functions
-"""
-
-from structlog import get_logger
-from consul import Consul
-from random import randint
-from adapters.common.utils.nethelpers import get_my_primary_local_ipv4
-
-log = get_logger()
-
-
-def connect_to_consult(consul_endpoint):
- log.debug('getting-service-endpoint', consul=consul_endpoint)
-
- host = consul_endpoint.split(':')[0].strip()
- port = int(consul_endpoint.split(':')[1].strip())
-
- return Consul(host=host, port=port)
-
-
-def verify_all_services_healthy(consul_endpoint, service_name=None,
- number_of_expected_services=None):
- """
- Verify in consul if any service is healthy
- :param consul_endpoint: a <host>:<port> string
- :param service_name: name of service to check, optional
- :param number_of_expected_services number of services to check for, optional
- :return: true if healthy, false otherwise
- """
-
- def check_health(service):
- _, serv_health = consul.health.service(service, passing=True)
- return not serv_health == []
-
- consul = connect_to_consult(consul_endpoint)
-
- if service_name is not None:
- return check_health(service_name)
-
- services = get_all_services(consul_endpoint)
-
- items = services.keys()
-
- if number_of_expected_services is not None and \
- len(items) != number_of_expected_services:
- return False
-
- for item in items:
- if not check_health(item):
- return False
-
- return True
-
-
-def get_all_services(consul_endpoint):
- log.debug('getting-service-verify-health')
-
- consul = connect_to_consult(consul_endpoint)
- _, services = consul.catalog.services()
-
- return services
-
-
-def get_all_instances_of_service(consul_endpoint, service_name):
- log.debug('getting-all-instances-of-service', service=service_name)
-
- consul = connect_to_consult(consul_endpoint)
- _, services = consul.catalog.service(service_name)
-
- for service in services:
- log.debug('service',
- name=service['ServiceName'],
- serviceid=service['ServiceID'],
- serviceport=service['ServicePort'],
- createindex=service['CreateIndex'])
-
- return services
-
-
-def get_endpoint_from_consul(consul_endpoint, service_name):
- """
- Get endpoint of service_name from consul.
- :param consul_endpoint: a <host>:<port> string
- :param service_name: name of service for which endpoint
- needs to be found.
- :return: service endpoint if available, else exit.
- """
- log.debug('getting-service-info', service=service_name)
-
- consul = connect_to_consult(consul_endpoint)
- _, services = consul.catalog.service(service_name)
-
- if len(services) == 0:
- raise Exception(
- 'Cannot find service {} in consul'.format(service_name))
- os.exit(1)
-
- """ Get host IPV4 address
- """
- local_ipv4 = get_my_primary_local_ipv4()
- """ If host IP address from where the request came in matches
- the IP address of the requested service's host IP address,
- pick the endpoint
- """
- for i in range(len(services)):
- service = services[i]
- if service['ServiceAddress'] == local_ipv4:
- log.debug("picking address locally")
- endpoint = '{}:{}'.format(service['ServiceAddress'],
- service['ServicePort'])
- return endpoint
-
- """ If service is not available locally, picak a random
- endpoint for the service from the list
- """
- service = services[randint(0, len(services) - 1)]
- endpoint = '{}:{}'.format(service['ServiceAddress'],
- service['ServicePort'])
-
- return endpoint
-
-
-def get_healthy_instances(consul_endpoint, service_name=None,
- number_of_expected_services=None):
- """
- Verify in consul if any service is healthy
- :param consul_endpoint: a <host>:<port> string
- :param service_name: name of service to check, optional
- :param number_of_expected_services number of services to check for, optional
- :return: true if healthy, false otherwise
- """
-
- def check_health(service):
- _, serv_health = consul.health.service(service, passing=True)
- return not serv_health == []
-
- consul = connect_to_consult(consul_endpoint)
-
- if service_name is not None:
- return check_health(service_name)
-
- services = get_all_services(consul_endpoint)
-
- items = services.keys()
-
- if number_of_expected_services is not None and \
- len(items) != number_of_expected_services:
- return False
-
- for item in items:
- if not check_health(item):
- return False
-
- return True
-
-
-if __name__ == '__main__':
- # print get_endpoint_from_consul('10.100.198.220:8500', 'kafka')
- # print get_healthy_instances('10.100.198.220:8500', 'voltha-health')
- # print get_healthy_instances('10.100.198.220:8500')
- get_all_instances_of_service('10.100.198.220:8500', 'voltha-grpc')
diff --git a/python/adapters/common/utils/deferred_utils.py b/python/adapters/common/utils/deferred_utils.py
deleted file mode 100644
index 3c55c1a..0000000
--- a/python/adapters/common/utils/deferred_utils.py
+++ /dev/null
@@ -1,56 +0,0 @@
-# Copyright 2017-present Open Networking Foundation
-#
-# 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 twisted.internet import reactor
-from twisted.internet.defer import Deferred
-from twisted.internet.error import AlreadyCalled
-
-
-class TimeOutError(Exception): pass
-
-
-class DeferredWithTimeout(Deferred):
- """
- Deferred with a timeout. If neither the callback nor the errback method
- is not called within the given time, the deferred's errback will be called
- with a TimeOutError() exception.
-
- All other uses are the same as of Deferred().
- """
- def __init__(self, timeout=1.0):
- Deferred.__init__(self)
- self._timeout = timeout
- self.timer = reactor.callLater(timeout, self.timed_out)
-
- def timed_out(self):
- self.errback(
- TimeOutError('timed out after {} seconds'.format(self._timeout)))
-
- def callback(self, result):
- self._cancel_timer()
- return Deferred.callback(self, result)
-
- def errback(self, fail):
- self._cancel_timer()
- return Deferred.errback(self, fail)
-
- def cancel(self):
- self._cancel_timer()
- return Deferred.cancel(self)
-
- def _cancel_timer(self):
- try:
- self.timer.cancel()
- except AlreadyCalled:
- pass
-
diff --git a/python/adapters/common/utils/dockerhelpers.py b/python/adapters/common/utils/dockerhelpers.py
deleted file mode 100644
index 4620aef..0000000
--- a/python/adapters/common/utils/dockerhelpers.py
+++ /dev/null
@@ -1,75 +0,0 @@
-#
-# Copyright 2017 the original author or authors.
-#
-# 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.
-#
-
-"""
-Some docker related convenience functions
-"""
-from datetime import datetime
-from concurrent.futures import ThreadPoolExecutor
-
-import os
-import socket
-from structlog import get_logger
-
-from docker import Client, errors
-
-
-docker_socket = os.environ.get('DOCKER_SOCK', 'unix://tmp/docker.sock')
-log = get_logger()
-
-def get_my_containers_name():
- """
- Return the docker containers name in which this process is running.
- To look up the container name, we use the container ID extracted from the
- $HOSTNAME environment variable (which is set by docker conventions).
- :return: String with the docker container name (or None if any issue is
- encountered)
- """
- my_container_id = os.environ.get('HOSTNAME', None)
-
- try:
- docker_cli = Client(base_url=docker_socket)
- info = docker_cli.inspect_container(my_container_id)
-
- except Exception, e:
- log.exception('failed', my_container_id=my_container_id, e=e)
- raise
-
- name = info['Name'].lstrip('/')
-
- return name
-
-def get_all_running_containers():
- try:
- docker_cli = Client(base_url=docker_socket)
- containers = docker_cli.containers()
-
- except Exception, e:
- log.exception('failed', e=e)
- raise
-
- return containers
-
-def inspect_container(id):
- try:
- docker_cli = Client(base_url=docker_socket)
- info = docker_cli.inspect_container(id)
- except Exception, e:
- log.exception('failed-inspect-container', id=id, e=e)
- raise
-
- return info
-
diff --git a/python/adapters/common/utils/grpc_utils.py b/python/adapters/common/utils/grpc_utils.py
deleted file mode 100644
index 8df630e..0000000
--- a/python/adapters/common/utils/grpc_utils.py
+++ /dev/null
@@ -1,109 +0,0 @@
-#
-# Copyright 2017 the original author or authors.
-#
-# 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.
-#
-
-"""
-Utilities to handle gRPC server and client side code in a Twisted environment
-"""
-import structlog
-from concurrent.futures import Future
-from twisted.internet import reactor
-from twisted.internet.defer import Deferred
-from twisted.python.threadable import isInIOThread
-
-
-log = structlog.get_logger()
-
-
-def twisted_async(func):
- """
- This decorator can be used to implement a gRPC method on the twisted
- thread, allowing asynchronous programming in Twisted while serving
- a gRPC call.
-
- gRPC methods normally are called on the futures.ThreadPool threads,
- so these methods cannot directly use Twisted protocol constructs.
- If the implementation of the methods needs to touch Twisted, it is
- safer (or mandatory) to wrap the method with this decorator, which will
- call the inner method from the external thread and ensure that the
- result is passed back to the foreign thread.
-
- Example usage:
-
- When implementing a gRPC server, typical pattern is:
-
- class SpamService(SpamServicer):
-
- def GetBadSpam(self, request, context):
- '''this is called from a ThreadPoolExecutor thread'''
- # generally unsafe to make Twisted calls
-
- @twisted_async
- def GetSpamSafely(self, request, context):
- '''this method now is executed on the Twisted main thread
- # safe to call any Twisted protocol functions
-
- @twisted_async
- @inlineCallbacks
- def GetAsyncSpam(self, request, context):
- '''this generator can use inlineCallbacks Twisted style'''
- result = yield some_async_twisted_call(request)
- returnValue(result)
-
- """
- def in_thread_wrapper(*args, **kw):
-
- if isInIOThread():
-
- return func(*args, **kw)
-
- f = Future()
-
- def twisted_wrapper():
- try:
- d = func(*args, **kw)
- if isinstance(d, Deferred):
-
- def _done(result):
- f.set_result(result)
- f.done()
-
- def _error(e):
- f.set_exception(e)
- f.done()
-
- d.addCallback(_done)
- d.addErrback(_error)
-
- else:
- f.set_result(d)
- f.done()
-
- except Exception, e:
- f.set_exception(e)
- f.done()
-
- reactor.callFromThread(twisted_wrapper)
- try:
- result = f.result()
- except Exception, e:
- log.exception(e=e, func=func, args=args, kw=kw)
- raise
-
- return result
-
- return in_thread_wrapper
-
-
diff --git a/python/adapters/common/utils/id_generation.py b/python/adapters/common/utils/id_generation.py
deleted file mode 100644
index e0fea1c..0000000
--- a/python/adapters/common/utils/id_generation.py
+++ /dev/null
@@ -1,116 +0,0 @@
-#
-# Copyright 2017 the original author or authors.
-#
-# 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.
-#
-# """ ID generation utils """
-
-from uuid import uuid4
-
-
-BROADCAST_CORE_ID=hex(0xFFFF)[2:]
-
-def get_next_core_id(current_id_in_hex_str):
- """
- :param current_id_in_hex_str: a hex string of the maximum core id
- assigned without the leading 0x characters
- :return: current_id_in_hex_str + 1 in hex string
- """
- if not current_id_in_hex_str or current_id_in_hex_str == '':
- return '0001'
- else:
- return format(int(current_id_in_hex_str, 16) + 1, '04x')
-
-
-def create_cluster_logical_device_ids(core_id, switch_id):
- """
- Creates a logical device id and an OpenFlow datapath id that is unique
- across the Voltha cluster.
- The returned logical device id represents a 64 bits integer where the
- lower 48 bits is the switch id and the upper 16 bits is the core id. For
- the datapath id the core id is set to '0000' as it is not used for voltha
- core routing
- :param core_id: string
- :param switch_id:int
- :return: cluster logical device id and OpenFlow datapath id
- """
- switch_id = format(switch_id, '012x')
- core_in_hex=format(int(core_id, 16), '04x')
- ld_id = '{}{}'.format(core_in_hex[-4:], switch_id[-12:])
- dpid_id = '{}{}'.format('0000', switch_id[-12:])
- return ld_id, int(dpid_id, 16)
-
-def is_broadcast_core_id(id):
- assert id and len(id) == 16
- return id[:4] == BROADCAST_CORE_ID
-
-def create_empty_broadcast_id():
- """
- Returns an empty broadcast id (ffff000000000000). The id is used to
- dispatch xPON objects across all the Voltha instances.
- :return: An empty broadcast id
- """
- return '{}{}'.format(BROADCAST_CORE_ID, '0'*12)
-
-def create_cluster_id():
- """
- Returns an id that is common across all voltha instances. The id
- is a str of 64 bits. The lower 48 bits refers to an id specific to that
- object while the upper 16 bits refers a broadcast core_id
- :return: An common id across all Voltha instances
- """
- return '{}{}'.format(BROADCAST_CORE_ID, uuid4().hex[:12])
-
-def create_cluster_device_id(core_id):
- """
- Creates a device id that is unique across the Voltha cluster.
- The device id is a str of 64 bits. The lower 48 bits refers to the
- device id while the upper 16 bits refers to the core id.
- :param core_id: string
- :return: cluster device id
- """
- return '{}{}'.format(format(int(core_id), '04x'), uuid4().hex[:12])
-
-
-def get_core_id_from_device_id(device_id):
- # Device id is a string and the first 4 characters represent the core_id
- assert device_id and len(device_id) == 16
- # Get the leading 4 hexs and remove leading 0's
- return device_id[:4]
-
-
-def get_core_id_from_logical_device_id(logical_device_id):
- """
- Logical Device id is a string and the first 4 characters represent the
- core_id
- :param logical_device_id:
- :return: core_id string
- """
- assert logical_device_id and len(logical_device_id) == 16
- # Get the leading 4 hexs and remove leading 0's
- return logical_device_id[:4]
-
-
-def get_core_id_from_datapath_id(datapath_id):
- """
- datapath id is a uint64 where:
- - low 48 bits -> switch_id
- - high 16 bits -> core id
- :param datapath_id:
- :return: core_id string
- """
- assert datapath_id
- # Get the hex string and remove the '0x' prefix
- id_in_hex_str = hex(datapath_id)[2:]
- assert len(id_in_hex_str) > 12
- return id_in_hex_str[:-12]
diff --git a/python/adapters/common/utils/indexpool.py b/python/adapters/common/utils/indexpool.py
deleted file mode 100644
index 858cb3a..0000000
--- a/python/adapters/common/utils/indexpool.py
+++ /dev/null
@@ -1,64 +0,0 @@
-# Copyright 2017-present Open Networking Foundation
-#
-# 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 bitstring import BitArray
-import structlog
-
-log = structlog.get_logger()
-
-class IndexPool(object):
- def __init__(self, max_entries, offset):
- self.max_entries = max_entries
- self.offset = offset
- self.indices = BitArray(self.max_entries)
-
- def get_next(self):
- try:
- _pos = self.indices.find('0b0')
- self.indices.set(1, _pos)
- return self.offset + _pos[0]
- except IndexError:
- log.info("exception-fail-to-allocate-id-all-bits-in-use")
- return None
-
- def allocate(self, index):
- try:
- _pos = index - self.offset
- if not (0 <= _pos < self.max_entries):
- log.info("{}-out-of-range".format(index))
- return None
- if self.indices[_pos]:
- log.info("{}-is-already-allocated".format(index))
- return None
- self.indices.set(1, _pos)
- return index
-
- except IndexError:
- return None
-
- def release(self, index):
- index -= self.offset
- _pos = (index,)
- try:
- self.indices.set(0, _pos)
- except IndexError:
- log.info("bit-position-{}-out-of-range".format(index))
-
- #index or multiple indices to set all of them to 1 - need to be a tuple
- def pre_allocate(self, index):
- if(isinstance(index, tuple)):
- _lst = list(index)
- for i in range(len(_lst)):
- _lst[i] -= self.offset
- index = tuple(_lst)
- self.indices.set(1, index)
diff --git a/python/adapters/common/utils/json_format.py b/python/adapters/common/utils/json_format.py
deleted file mode 100644
index c18d013..0000000
--- a/python/adapters/common/utils/json_format.py
+++ /dev/null
@@ -1,105 +0,0 @@
-# Copyright 2017-present Open Networking Foundation
-#
-# 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.
-"""
-Monkey patched json_format to allow best effort decoding of Any fields.
-Use the additional flag (strict_any_handling=False) to trigger the
-best-effort behavior. Omit the flag, or just use the original json_format
-module fot the strict behavior.
-"""
-
-from google.protobuf import json_format
-
-class _PatchedPrinter(json_format._Printer):
-
- def __init__(self, including_default_value_fields=False,
- preserving_proto_field_name=False,
- strict_any_handling=False):
- super(_PatchedPrinter, self).__init__(including_default_value_fields,
- preserving_proto_field_name)
- self.strict_any_handling = strict_any_handling
-
- def _BestEffortAnyMessageToJsonObject(self, msg):
- try:
- res = self._AnyMessageToJsonObject(msg)
- except TypeError:
- res = self._RegularMessageToJsonObject(msg, {})
- return res
-
-
-def MessageToDict(message,
- including_default_value_fields=False,
- preserving_proto_field_name=False,
- strict_any_handling=False):
- """Converts protobuf message to a JSON dictionary.
-
- Args:
- message: The protocol buffers message instance to serialize.
- including_default_value_fields: If True, singular primitive fields,
- repeated fields, and map fields will always be serialized. If
- False, only serialize non-empty fields. Singular message fields
- and oneof fields are not affected by this option.
- preserving_proto_field_name: If True, use the original proto field
- names as defined in the .proto file. If False, convert the field
- names to lowerCamelCase.
- strict_any_handling: If True, converion will error out (like in the
- original method) if an Any field with value for which the Any type
- is not loaded is encountered. If False, the conversion will leave
- the field un-packed, but otherwise will continue.
-
- Returns:
- A dict representation of the JSON formatted protocol buffer message.
- """
- printer = _PatchedPrinter(including_default_value_fields,
- preserving_proto_field_name,
- strict_any_handling=strict_any_handling)
- # pylint: disable=protected-access
- return printer._MessageToJsonObject(message)
-
-
-def MessageToJson(message,
- including_default_value_fields=False,
- preserving_proto_field_name=False,
- strict_any_handling=False):
- """Converts protobuf message to JSON format.
-
- Args:
- message: The protocol buffers message instance to serialize.
- including_default_value_fields: If True, singular primitive fields,
- repeated fields, and map fields will always be serialized. If
- False, only serialize non-empty fields. Singular message fields
- and oneof fields are not affected by this option.
- preserving_proto_field_name: If True, use the original proto field
- names as defined in the .proto file. If False, convert the field
- names to lowerCamelCase.
- strict_any_handling: If True, converion will error out (like in the
- original method) if an Any field with value for which the Any type
- is not loaded is encountered. If False, the conversion will leave
- the field un-packed, but otherwise will continue.
-
- Returns:
- A string containing the JSON formatted protocol buffer message.
- """
- printer = _PatchedPrinter(including_default_value_fields,
- preserving_proto_field_name,
- strict_any_handling=strict_any_handling)
- return printer.ToJsonString(message)
-
-
-json_format._WKTJSONMETHODS['google.protobuf.Any'] = [
- '_BestEffortAnyMessageToJsonObject',
- '_ConvertAnyMessage'
-]
-
-json_format._Printer._BestEffortAnyMessageToJsonObject = \
- json_format._Printer._AnyMessageToJsonObject
diff --git a/python/adapters/common/utils/message_queue.py b/python/adapters/common/utils/message_queue.py
deleted file mode 100644
index 2b4257a..0000000
--- a/python/adapters/common/utils/message_queue.py
+++ /dev/null
@@ -1,89 +0,0 @@
-#
-# Copyright 2017 the original author or authors.
-#
-# 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 twisted.internet.defer import Deferred
-from twisted.internet.defer import succeed
-
-
-class MessageQueue(object):
- """
- An event driven queue, similar to twisted.internet.defer.DeferredQueue
- but which allows selective dequeing based on a predicate function.
- Unlike DeferredQueue, there is no limit on backlog, and there is no queue
- limit.
- """
-
- def __init__(self):
- self.waiting = [] # tuples of (d, predicate)
- self.queue = [] # messages piling up here if no one is waiting
-
- def reset(self):
- """
- Purge all content as well as waiters (by errback-ing their entries).
- :return: None
- """
- for d, _ in self.waiting:
- d.errback(Exception('mesage queue reset() was called'))
- self.waiting = []
- self.queue = []
-
- def _cancelGet(self, d):
- """
- Remove a deferred from our waiting list.
- :param d: The deferred that was been canceled.
- :return: None
- """
- for i in range(len(self.waiting)):
- if self.waiting[i][0] is d:
- self.waiting.pop(i)
-
- def put(self, obj):
- """
- Add an object to this queue
- :param obj: arbitrary object that will be added to the queue
- :return:
- """
-
- # if someone is waiting for this, return right away
- for i in range(len(self.waiting)):
- d, predicate = self.waiting[i]
- if predicate is None or predicate(obj):
- self.waiting.pop(i)
- d.callback(obj)
- return
-
- # otherwise...
- self.queue.append(obj)
-
- def get(self, predicate=None):
- """
- Attempt to retrieve and remove an object from the queue that
- matches the optional predicate.
- :return: Deferred which fires with the next object available.
- If predicate was provided, only objects for which
- predicate(obj) is True will be considered.
- """
- for i in range(len(self.queue)):
- msg = self.queue[i]
- if predicate is None or predicate(msg):
- self.queue.pop(i)
- return succeed(msg)
-
- # there were no matching entries if we got here, so we wait
- d = Deferred(canceller=self._cancelGet)
- self.waiting.append((d, predicate))
- return d
-
-
diff --git a/python/adapters/common/utils/nethelpers.py b/python/adapters/common/utils/nethelpers.py
deleted file mode 100644
index b17aced..0000000
--- a/python/adapters/common/utils/nethelpers.py
+++ /dev/null
@@ -1,86 +0,0 @@
-#
-# Copyright 2017 the original author or authors.
-#
-# 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.
-#
-
-"""
-Some network related convenience functions
-"""
-
-from netifaces import AF_INET
-
-import netifaces as ni
-import netaddr
-
-
-def _get_all_interfaces():
- m_interfaces = []
- for iface in ni.interfaces():
- m_interfaces.append((iface, ni.ifaddresses(iface)))
- return m_interfaces
-
-
-def _get_my_primary_interface():
- gateways = ni.gateways()
- assert 'default' in gateways, \
- ("No default gateway on host/container, "
- "cannot determine primary interface")
- default_gw_index = gateways['default'].keys()[0]
- # gateways[default_gw_index] has the format (example):
- # [('10.15.32.1', 'en0', True)]
- interface_name = gateways[default_gw_index][0][1]
- return interface_name
-
-
-def get_my_primary_local_ipv4(inter_core_subnet=None, ifname=None):
- if not inter_core_subnet:
- return _get_my_primary_local_ipv4(ifname)
- # My IP should belong to the specified subnet
- for iface in ni.interfaces():
- addresses = ni.ifaddresses(iface)
- if AF_INET in addresses:
- m_ip = addresses[AF_INET][0]['addr']
- _ip = netaddr.IPAddress(m_ip).value
- m_network = netaddr.IPNetwork(inter_core_subnet)
- if _ip >= m_network.first and _ip <= m_network.last:
- return m_ip
- return None
-
-
-def get_my_primary_interface(pon_subnet=None):
- if not pon_subnet:
- return _get_my_primary_interface()
- # My interface should have an IP that belongs to the specified subnet
- for iface in ni.interfaces():
- addresses = ni.ifaddresses(iface)
- if AF_INET in addresses:
- m_ip = addresses[AF_INET][0]['addr']
- m_ip = netaddr.IPAddress(m_ip).value
- m_network = netaddr.IPNetwork(pon_subnet)
- if m_ip >= m_network.first and m_ip <= m_network.last:
- return iface
- return None
-
-
-def _get_my_primary_local_ipv4(ifname=None):
- try:
- ifname = get_my_primary_interface() if ifname is None else ifname
- addresses = ni.ifaddresses(ifname)
- ipv4 = addresses[AF_INET][0]['addr']
- return ipv4
- except Exception as e:
- return None
-
-if __name__ == '__main__':
- print get_my_primary_local_ipv4()
diff --git a/python/adapters/common/utils/ordered_weakvalue_dict.py b/python/adapters/common/utils/ordered_weakvalue_dict.py
deleted file mode 100644
index 9ea739a..0000000
--- a/python/adapters/common/utils/ordered_weakvalue_dict.py
+++ /dev/null
@@ -1,48 +0,0 @@
-#
-# Copyright 2017 the original author or authors.
-#
-# 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 _weakref import ref
-from weakref import KeyedRef
-from collections import OrderedDict
-
-
-class OrderedWeakValueDict(OrderedDict):
- """
- Modified OrderedDict to use weak references as values. Entries disappear
- automatically if the referred value has no more strong reference pointing
- ot it.
-
- Warning, this is not a complete implementation, only what is needed for
- now. See test_ordered_wealvalue_dict.py to see what is tested behavior.
- """
- def __init__(self, *args, **kw):
- def remove(wr, selfref=ref(self)):
- self = selfref()
- if self is not None:
- super(OrderedWeakValueDict, self).__delitem__(wr.key)
- self._remove = remove
- super(OrderedWeakValueDict, self).__init__(*args, **kw)
-
- def __setitem__(self, key, value):
- super(OrderedWeakValueDict, self).__setitem__(
- key, KeyedRef(value, self._remove, key))
-
- def __getitem__(self, key):
- o = super(OrderedWeakValueDict, self).__getitem__(key)()
- if o is None:
- raise KeyError, key
- else:
- return o
-
diff --git a/python/adapters/docker/Dockerfile.adapter_ponsim_olt b/python/adapters/docker/Dockerfile.adapter_ponsim_olt
deleted file mode 100644
index 209200d..0000000
--- a/python/adapters/docker/Dockerfile.adapter_ponsim_olt
+++ /dev/null
@@ -1,42 +0,0 @@
-# Copyright 2016 the original author or authors.
-#
-# 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.
-ARG TAG=latest
-ARG REGISTRY=
-ARG REPOSITORY=
-
-FROM ${REGISTRY}${REPOSITORY}voltha-protos:${TAG} as protos
-FROM ${REGISTRY}${REPOSITORY}voltha-base:${TAG}
-
-MAINTAINER Voltha Community <info@opennetworking.org>
-
-# Bundle app source
-RUN mkdir /adapters && touch /adapters/__init__.py
-ENV PYTHONPATH=/adapters
-COPY common /adapters/adapters/common
-COPY kafka /adapters/adapters/kafka
-COPY ./*.py /adapters/adapters/
-#COPY pki /voltha/pki
-COPY ponsim_olt /adapters/adapters/ponsim_olt
-RUN touch /adapters/adapters/__init__.py
-
-
-# Copy in the generated GRPC proto code
-COPY --from=protos /protos/voltha /adapters/adapters/protos
-COPY --from=protos /protos/google/api /adapters/adapters/protos/third_party/google/api
-COPY protos/third_party/__init__.py /adapters/adapters/protos/third_party
-RUN touch /adapters/adapters/protos/__init__.py
-RUN touch /adapters/adapters/protos/third_party/google/__init__.py
-
-# Exposing process and default entry point
-# CMD ["python", "/adapters/ponsim_olt/main.py"]
diff --git a/python/adapters/docker/Dockerfile.adapter_ponsim_onu b/python/adapters/docker/Dockerfile.adapter_ponsim_onu
deleted file mode 100644
index d0d3e36..0000000
--- a/python/adapters/docker/Dockerfile.adapter_ponsim_onu
+++ /dev/null
@@ -1,42 +0,0 @@
-# Copyright 2016 the original author or authors.
-#
-# 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.
-ARG TAG=latest
-ARG REGISTRY=
-ARG REPOSITORY=
-
-FROM ${REGISTRY}${REPOSITORY}voltha-protos:${TAG} as protos
-FROM ${REGISTRY}${REPOSITORY}voltha-base:${TAG}
-
-MAINTAINER Voltha Community <info@opennetworking.org>
-
-# Bundle app source
-RUN mkdir /adapters && touch /adapters/__init__.py
-ENV PYTHONPATH=/adapters
-COPY common /adapters/adapters/common
-COPY kafka /adapters/adapters/kafka
-COPY ./*.py /adapters/adapters/
-#COPY pki /voltha/pki
-COPY ponsim_onu /adapters/adapters/ponsim_onu
-RUN touch /adapters/adapters/__init__.py
-
-
-# Copy in the generated GRPC proto code
-COPY --from=protos /protos/voltha /adapters/adapters/protos
-COPY --from=protos /protos/google/api /adapters/adapters/protos/third_party/google/api
-COPY protos/third_party/__init__.py /adapters/adapters/protos/third_party
-RUN touch /adapters/adapters/protos/__init__.py
-RUN touch /adapters/adapters/protos/third_party/google/__init__.py
-
-# Exposing process and default entry point
-# CMD ["python", "/adapters/ponsim_onu/main.py"]
diff --git a/python/adapters/docker/Dockerfile.base b/python/adapters/docker/Dockerfile.base
deleted file mode 100644
index 1b912e0..0000000
--- a/python/adapters/docker/Dockerfile.base
+++ /dev/null
@@ -1,34 +0,0 @@
-# Copyright 2016 the original author or authors.
-#
-# 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:xenial
-
-MAINTAINER Voltha Community <info@opennetworking.org>
-
-# Update to have latest images
-RUN apt-get update && \
- apt-get install -y python python-pip openssl iproute2 libpcap-dev wget
-
-COPY requirements.txt /tmp/requirements.txt
-
-# pip install cython enum34 six && \
-# Install app dependencies
-RUN wget https://github.com/Yelp/dumb-init/releases/download/v1.2.0/dumb-init_1.2.0_amd64.deb && \
- dpkg -i *.deb && \
- rm -f *.deb && \
- apt-get update && \
- apt-get install -y wget build-essential make gcc binutils python-dev libffi-dev libssl-dev git && \
- pip install -r /tmp/requirements.txt && \
- apt-get purge -y wget build-essential make gcc binutils python-dev libffi-dev libssl-dev git && \
- apt-get autoremove -y
diff --git a/python/adapters/iadapter.py b/python/adapters/iadapter.py
index ee4d116..04cb303 100644
--- a/python/adapters/iadapter.py
+++ b/python/adapters/iadapter.py
@@ -22,13 +22,13 @@
from twisted.internet import reactor
from zope.interface import implementer
-from adapters.interface import IAdapterInterface
-from adapters.protos.adapter_pb2 import Adapter
-from adapters.protos.adapter_pb2 import AdapterConfig
-from adapters.protos.common_pb2 import AdminState
-from adapters.protos.common_pb2 import LogLevel
-from adapters.protos.device_pb2 import DeviceType, DeviceTypes
-from adapters.protos.health_pb2 import HealthStatus
+from interface import IAdapterInterface
+from python.protos.adapter_pb2 import Adapter
+from python.protos.adapter_pb2 import AdapterConfig
+from python.protos.common_pb2 import AdminState
+from python.protos.common_pb2 import LogLevel
+from python.protos.device_pb2 import DeviceType, DeviceTypes
+from python.protos.health_pb2 import HealthStatus
log = structlog.get_logger()
@@ -273,7 +273,7 @@
handler.send_proxied_message(proxy_address, msg)
def process_inter_adapter_message(self, msg):
- log.info('process-inter-adapter-message', msg=msg)
+ log.debug('process-inter-adapter-message', msg=msg)
# Unpack the header to know which device needs to handle this message
handler = None
if msg.header.proxy_device_id:
@@ -286,18 +286,11 @@
if handler:
reactor.callLater(0, handler.process_inter_adapter_message, msg)
- def receive_packet_out(self, logical_device_id, egress_port_no, msg):
- def ldi_to_di(ldi):
- di = self.logical_device_id_to_root_device_id.get(ldi)
- if di is None:
- logical_device = self.core_proxy.get_logical_device(ldi)
- di = logical_device.root_device_id
- self.logical_device_id_to_root_device_id[ldi] = di
- return di
-
- device_id = ldi_to_di(logical_device_id)
+ def receive_packet_out(self, device_id, egress_port_no, msg):
+ log.info('receive_packet_out', device_id=device_id,
+ egress_port=egress_port_no, msg=msg)
handler = self.devices_handlers[device_id]
- handler.packet_out(egress_port_no, msg)
+ handler.packet_out(egress_port_no, msg.data)
"""
diff --git a/python/adapters/kafka/adapter_proxy.py b/python/adapters/kafka/adapter_proxy.py
index fad1093..769de80 100644
--- a/python/adapters/kafka/adapter_proxy.py
+++ b/python/adapters/kafka/adapter_proxy.py
@@ -21,9 +21,9 @@
import structlog
from uuid import uuid4
from twisted.internet.defer import inlineCallbacks, returnValue
-from adapters.kafka.container_proxy import ContainerProxy
-from adapters.protos import third_party
-from adapters.protos.core_adapter_pb2 import InterAdapterHeader, \
+from container_proxy import ContainerProxy
+from python.protos import third_party
+from python.protos.core_adapter_pb2 import InterAdapterHeader, \
InterAdapterMessage
import time
diff --git a/python/adapters/kafka/adapter_request_facade.py b/python/adapters/kafka/adapter_request_facade.py
index 67f7869..cbae56d 100644
--- a/python/adapters/kafka/adapter_request_facade.py
+++ b/python/adapters/kafka/adapter_request_facade.py
@@ -22,11 +22,11 @@
from twisted.internet.defer import inlineCallbacks
from zope.interface import implementer
-from adapters.interface import IAdapterInterface
-from adapters.protos.core_adapter_pb2 import IntType, InterAdapterMessage
-from adapters.protos.device_pb2 import Device
-from adapters.protos.openflow_13_pb2 import FlowChanges, FlowGroups, Flows, \
- FlowGroupChanges
+from python.adapters.interface import IAdapterInterface
+from python.protos.core_adapter_pb2 import IntType, InterAdapterMessage, StrType, Error, ErrorCode
+from python.protos.device_pb2 import Device
+from python.protos.openflow_13_pb2 import FlowChanges, FlowGroups, Flows, \
+ FlowGroupChanges, ofp_packet_out
class MacAddressError(BaseException):
@@ -68,7 +68,8 @@
device.Unpack(d)
return True, self.adapter.adopt_device(d)
else:
- return False, d
+ return False, Error(code=ErrorCode.INVALID_PARAMETERS,
+ reason="device-invalid")
def get_ofp_device_info(self, device):
d = Device()
@@ -76,17 +77,22 @@
device.Unpack(d)
return True, self.adapter.get_ofp_device_info(d)
else:
- return False, d
+ return False, Error(code=ErrorCode.INVALID_PARAMETERS,
+ reason="device-invalid")
def get_ofp_port_info(self, device, port_no):
d = Device()
if device:
device.Unpack(d)
else:
- return (False, d)
-
+ return False, Error(code=ErrorCode.INVALID_PARAMETERS,
+ reason="device-invalid")
p = IntType()
- port_no.Unpack(p)
+ if port_no:
+ port_no.Unpack(p)
+ else:
+ return False, Error(code=ErrorCode.INVALID_PARAMETERS,
+ reason="port-no-invalid")
return True, self.adapter.get_ofp_port_info(d, p.val)
@@ -102,7 +108,8 @@
device.Unpack(d)
return True, self.adapter.disable_device(d)
else:
- return False, d
+ return False, Error(code=ErrorCode.INVALID_PARAMETERS,
+ reason="device-invalid")
def reenable_device(self, device):
d = Device()
@@ -110,7 +117,8 @@
device.Unpack(d)
return True, self.adapter.reenable_device(d)
else:
- return False, d
+ return False, Error(code=ErrorCode.INVALID_PARAMETERS,
+ reason="device-invalid")
def reboot_device(self, device):
d = Device()
@@ -118,7 +126,8 @@
device.Unpack(d)
return (True, self.adapter.reboot_device(d))
else:
- return (False, d)
+ return False, Error(code=ErrorCode.INVALID_PARAMETERS,
+ reason="device-invalid")
def download_image(self, device, request):
return self.adapter.download_image(device, request)
@@ -144,7 +153,8 @@
device.Unpack(d)
return (True, self.adapter.delete_device(d))
else:
- return (False, d)
+ return False, Error(code=ErrorCode.INVALID_PARAMETERS,
+ reason="device-invalid")
def get_device_details(self, device):
return self.adapter.get_device_details(device)
@@ -154,8 +164,8 @@
if device:
device.Unpack(d)
else:
- return (False, d)
-
+ return False, Error(code=ErrorCode.INVALID_PARAMETERS,
+ reason="device-invalid")
f = Flows()
if flows:
flows.Unpack(f)
@@ -171,8 +181,8 @@
if device:
device.Unpack(d)
else:
- return (False, d)
-
+ return False, Error(code=ErrorCode.INVALID_PARAMETERS,
+ reason="device-invalid")
f = FlowChanges()
if flow_changes:
flow_changes.Unpack(f)
@@ -194,6 +204,33 @@
if msg:
msg.Unpack(m)
else:
- return (False, m)
+ return False, Error(code=ErrorCode.INVALID_PARAMETERS,
+ reason="msg-invalid")
return (True, self.adapter.process_inter_adapter_message(m))
+
+
+ def receive_packet_out(self, deviceId, outPort, packet):
+ d_id = StrType()
+ if deviceId:
+ deviceId.Unpack(d_id)
+ else:
+ return False, Error(code=ErrorCode.INVALID_PARAMETERS,
+ reason="deviceid-invalid")
+
+ op = IntType
+ if outPort:
+ outPort.Unpack(op)
+ else:
+ return False, Error(code=ErrorCode.INVALID_PARAMETERS,
+ reason="outport-invalid")
+
+ p = ofp_packet_out()
+ if packet:
+ packet.Unpack(p)
+ else:
+ return False, Error(code=ErrorCode.INVALID_PARAMETERS,
+ reason="packet-invalid")
+
+ return (True, self.adapter.receive_packet_out(d_id, op, p))
+
diff --git a/python/adapters/kafka/container_proxy.py b/python/adapters/kafka/container_proxy.py
index 79918cd..8c4e828 100644
--- a/python/adapters/kafka/container_proxy.py
+++ b/python/adapters/kafka/container_proxy.py
@@ -23,9 +23,9 @@
from twisted.python import failure
from zope.interface import implementer
-from adapters.common.utils.deferred_utils import DeferredWithTimeout, \
+from python.common.utils.deferred_utils import DeferredWithTimeout, \
TimeOutError
-from adapters.common.utils.registry import IComponent
+from python.common.utils.registry import IComponent
log = structlog.get_logger()
diff --git a/python/adapters/kafka/core_proxy.py b/python/adapters/kafka/core_proxy.py
index cc3f081..4bab30d 100644
--- a/python/adapters/kafka/core_proxy.py
+++ b/python/adapters/kafka/core_proxy.py
@@ -21,11 +21,11 @@
from google.protobuf.message import Message
from twisted.internet.defer import inlineCallbacks, returnValue
-from adapters.kafka.container_proxy import ContainerProxy
-from adapters.protos.common_pb2 import ID, ConnectStatus, OperStatus
-from adapters.protos.core_adapter_pb2 import StrType, BoolType, IntType
-from adapters.protos.device_pb2 import Device, Ports
-from adapters.protos.voltha_pb2 import CoreInstance
+from container_proxy import ContainerProxy
+from python.protos.common_pb2 import ID, ConnectStatus, OperStatus
+from python.protos.core_adapter_pb2 import StrType, BoolType, IntType, Packet
+from python.protos.device_pb2 import Device, Ports
+from python.protos.voltha_pb2 import CoreInstance
log = structlog.get_logger()
@@ -243,7 +243,8 @@
b = BoolType()
b.val = init
res = yield self.invoke(rpc="DevicePMConfigUpdate",
- device_pm_config=device_pm_config, init=b)
+ device_pm_config=device_pm_config,
+ init=b)
returnValue(res)
@ContainerProxy.wrap_request(None)
@@ -252,7 +253,8 @@
log.debug("port_created")
proto_id = ID()
proto_id.id = device_id
- res = yield self.invoke(rpc="PortCreated", device_id=proto_id,
+ res = yield self.invoke(rpc="PortCreated",
+ device_id=proto_id,
port=port)
returnValue(res)
@@ -274,5 +276,16 @@
def image_download_deleted(img_dnld):
raise NotImplementedError()
- def packet_in(device_id, egress_port_no, packet):
- raise NotImplementedError()
+ def send_packet_in(self, device_id, port, packet):
+ log.debug("send_packet_in")
+ proto_id = ID()
+ proto_id.id = device_id
+ p = IntType()
+ p.val = port
+ pac = Packet()
+ pac.payload = packet
+ res = yield self.invoke(rpc="PacketIn",
+ device_id=proto_id,
+ port=p,
+ packet=pac)
+ returnValue(res)
diff --git a/python/adapters/kafka/event_bus_publisher.py b/python/adapters/kafka/event_bus_publisher.py
index 011fdea..89b3385 100644
--- a/python/adapters/kafka/event_bus_publisher.py
+++ b/python/adapters/kafka/event_bus_publisher.py
@@ -25,7 +25,7 @@
from google.protobuf.message import Message
from simplejson import dumps
-from adapters.common.event_bus import EventBusClient
+from python.common.event_bus import EventBusClient
log = structlog.get_logger()
diff --git a/python/adapters/kafka/kafka_inter_container_library.py b/python/adapters/kafka/kafka_inter_container_library.py
index 3f6f5eb..1d2b05c 100644
--- a/python/adapters/kafka/kafka_inter_container_library.py
+++ b/python/adapters/kafka/kafka_inter_container_library.py
@@ -25,10 +25,10 @@
DeferredQueue, gatherResults
from zope.interface import implementer
-from adapters.common.utils import asleep
-from adapters.common.utils.registry import IComponent
-from adapters.kafka.kafka_proxy import KafkaProxy, get_kafka_proxy
-from adapters.protos.core_adapter_pb2 import MessageType, Argument, \
+from python.common.utils import asleep
+from python.common.utils.registry import IComponent
+from kafka_proxy import KafkaProxy, get_kafka_proxy
+from python.protos.core_adapter_pb2 import MessageType, Argument, \
InterContainerRequestBody, InterContainerMessage, Header, \
InterContainerResponseBody
diff --git a/python/adapters/kafka/kafka_proxy.py b/python/adapters/kafka/kafka_proxy.py
index c11caa7..6dcb10f 100644
--- a/python/adapters/kafka/kafka_proxy.py
+++ b/python/adapters/kafka/kafka_proxy.py
@@ -23,9 +23,9 @@
from twisted.internet.defer import inlineCallbacks, returnValue
from zope.interface import implementer
-from adapters.common.utils.consulhelpers import get_endpoint_from_consul
-from adapters.common.utils.registry import IComponent
-from adapters.kafka.event_bus_publisher import EventBusPublisher
+from python.common.utils.consulhelpers import get_endpoint_from_consul
+from python.common.utils.registry import IComponent
+from event_bus_publisher import EventBusPublisher
log = get_logger()
diff --git a/python/adapters/ponsim_olt/main.py b/python/adapters/ponsim_olt/main.py
index 569e284..09b78fc 100755
--- a/python/adapters/ponsim_olt/main.py
+++ b/python/adapters/ponsim_olt/main.py
@@ -29,22 +29,22 @@
from twisted.internet.task import LoopingCall
from zope.interface import implementer
-from adapters.common.structlog_setup import setup_logging, update_logging
-from adapters.common.utils.asleep import asleep
-from adapters.common.utils.deferred_utils import TimeOutError
-from adapters.common.utils.dockerhelpers import get_my_containers_name
-from adapters.common.utils.nethelpers import get_my_primary_local_ipv4, \
+from python.common.structlog_setup import setup_logging, update_logging
+from python.common.utils.asleep import asleep
+from python.common.utils.deferred_utils import TimeOutError
+from python.common.utils.dockerhelpers import get_my_containers_name
+from python.common.utils.nethelpers import get_my_primary_local_ipv4, \
get_my_primary_interface
-from adapters.common.utils.registry import registry, IComponent
-from adapters.kafka.adapter_proxy import AdapterProxy
-from adapters.kafka.adapter_request_facade import AdapterRequestFacade
-from adapters.kafka.core_proxy import CoreProxy
-from adapters.kafka.kafka_inter_container_library import IKafkaMessagingProxy, \
+from python.common.utils.registry import registry, IComponent
+from python.adapters.kafka.adapter_proxy import AdapterProxy
+from python.adapters.kafka.adapter_request_facade import AdapterRequestFacade
+from python.adapters.kafka.core_proxy import CoreProxy
+from python.adapters.kafka.kafka_inter_container_library import IKafkaMessagingProxy, \
get_messaging_proxy
-from adapters.kafka.kafka_proxy import KafkaProxy, get_kafka_proxy
-from adapters.ponsim_olt.ponsim_olt import PonSimOltAdapter
-from adapters.protos import third_party
-from adapters.protos.adapter_pb2 import AdapterConfig
+from python.adapters.kafka.kafka_proxy import KafkaProxy, get_kafka_proxy
+from ponsim_olt import PonSimOltAdapter
+from python.protos import third_party
+from python.protos.adapter_pb2 import AdapterConfig
_ = third_party
diff --git a/python/adapters/ponsim_olt/ponsim_olt.py b/python/adapters/ponsim_olt/ponsim_olt.py
index 52fb63b..df834e5 100644
--- a/python/adapters/ponsim_olt/ponsim_olt.py
+++ b/python/adapters/ponsim_olt/ponsim_olt.py
@@ -23,6 +23,8 @@
import structlog
from google.protobuf.empty_pb2 import Empty
from google.protobuf.json_format import MessageToDict
+from scapy.layers.inet import Raw
+import json
from google.protobuf.message import Message
from grpc._channel import _Rendezvous
from scapy.layers.l2 import Ether, Dot1Q
@@ -31,25 +33,25 @@
from twisted.internet.defer import inlineCallbacks, returnValue
from twisted.internet.task import LoopingCall
-from adapters.common.frameio.frameio import BpfProgramFilter, hexify
-from adapters.common.utils.asleep import asleep
-from adapters.common.utils.registry import registry
-from adapters.iadapter import OltAdapter
-from adapters.kafka.kafka_proxy import get_kafka_proxy
-from adapters.protos import ponsim_pb2
-from adapters.protos import third_party
-from adapters.protos.common_pb2 import OperStatus, ConnectStatus
-from adapters.protos.core_adapter_pb2 import SwitchCapability, PortCapability, \
+from python.adapters.common.frameio.frameio import BpfProgramFilter, hexify
+from python.common.utils.asleep import asleep
+from python.common.utils.registry import registry
+from python.adapters.iadapter import OltAdapter
+from python.adapters.kafka.kafka_proxy import get_kafka_proxy
+from python.protos import ponsim_pb2
+from python.protos import third_party
+from python.protos.common_pb2 import OperStatus, ConnectStatus
+from python.protos.core_adapter_pb2 import SwitchCapability, PortCapability, \
InterAdapterMessageType, InterAdapterResponseBody
-from adapters.protos.device_pb2 import Port, PmConfig, PmConfigs
-from adapters.protos.events_pb2 import KpiEvent, KpiEventType, MetricValuePairs
-from adapters.protos.logical_device_pb2 import LogicalPort
-from adapters.protos.openflow_13_pb2 import OFPPS_LIVE, OFPPF_FIBER, \
+from python.protos.device_pb2 import Port, PmConfig, PmConfigs
+from python.protos.events_pb2 import KpiEvent, KpiEventType, MetricValuePairs
+from python.protos.logical_device_pb2 import LogicalPort
+from python.protos.openflow_13_pb2 import OFPPS_LIVE, OFPPF_FIBER, \
OFPPF_1GB_FD, \
OFPC_GROUP_STATS, OFPC_PORT_STATS, OFPC_TABLE_STATS, OFPC_FLOW_STATS, \
ofp_switch_features, ofp_desc
-from adapters.protos.openflow_13_pb2 import ofp_port
-from adapters.protos.ponsim_pb2 import FlowTable, PonSimFrame, PonSimMetricsRequest
+from python.protos.openflow_13_pb2 import ofp_port
+from python.protos.ponsim_pb2 import FlowTable, PonSimFrame, PonSimMetricsRequest
_ = third_party
log = structlog.get_logger()
@@ -157,33 +159,11 @@
self.device = device
self.lc = None
+ # TODO: Implement code to send to kafka cluster directly instead of
+ # going through the voltha core.
def send_alarm(self, context_data, alarm_data):
- try:
- current_context = {}
- for key, value in context_data.__dict__.items():
- current_context[key] = str(value)
-
- alarm_event = self.adapter.adapter_agent.create_alarm(
- resource_id=self.device.id,
- description="{}.{} - {}".format(self.adapter.name,
- self.device.id,
- alarm_data[
- 'description']) if 'description' in alarm_data else None,
- type=alarm_data['type'] if 'type' in alarm_data else None,
- category=alarm_data[
- 'category'] if 'category' in alarm_data else None,
- severity=alarm_data[
- 'severity'] if 'severity' in alarm_data else None,
- state=alarm_data['state'] if 'state' in alarm_data else None,
- raised_ts=alarm_data['ts'] if 'ts' in alarm_data else 0,
- context=current_context
- )
-
- self.adapter.adapter_agent.submit_alarm(self.device.id,
- alarm_event)
-
- except Exception as e:
- log.exception('failed-to-send-alarm', e=e)
+ log.debug("send-alarm-not-implemented")
+ return
class PonSimOltAdapter(OltAdapter):
@@ -379,6 +359,27 @@
def reconcile(self, device):
self.log.info('reconciling-OLT-device')
+ def _rcv_frame(self, frame):
+ pkt = Ether(frame)
+
+ if pkt.haslayer(Dot1Q):
+ outer_shim = pkt.getlayer(Dot1Q)
+
+ if isinstance(outer_shim.payload, Dot1Q):
+ inner_shim = outer_shim.payload
+ cvid = inner_shim.vlan
+ popped_frame = (
+ Ether(src=pkt.src, dst=pkt.dst, type=inner_shim.type) /
+ inner_shim.payload
+ )
+ self.log.info('sending-packet-in',device_id=self.device_id, port=cvid)
+ self.core_proxy.send_packet_in(device_id=self.device_id,
+ port=cvid,
+ packet=str(popped_frame))
+ elif pkt.haslayer(Raw):
+ raw_data = json.loads(pkt.getlayer(Raw).load)
+ self.alarms.send_alarm(self, raw_data)
+
@inlineCallbacks
def rcv_grpc(self):
"""
@@ -504,7 +505,7 @@
out_port = self.nni_port.port_no if egress_port == self.nni_port.port_no else 1
# send over grpc stream
- stub = ponsim_pb2.PonSimStub(self.get_channel())
+ stub = ponsim_pb2.PonSimStub(self.channel)
frame = PonSimFrame(id=self.device_id, payload=str(out_pkt),
out_port=out_port)
stub.SendFrame(frame)
diff --git a/python/adapters/ponsim_onu/main.py b/python/adapters/ponsim_onu/main.py
index 3f18e50..d6418e9 100755
--- a/python/adapters/ponsim_onu/main.py
+++ b/python/adapters/ponsim_onu/main.py
@@ -29,22 +29,22 @@
from twisted.internet.task import LoopingCall
from zope.interface import implementer
-from adapters.common.structlog_setup import setup_logging, update_logging
-from adapters.common.utils.asleep import asleep
-from adapters.common.utils.deferred_utils import TimeOutError
-from adapters.common.utils.dockerhelpers import get_my_containers_name
-from adapters.common.utils.nethelpers import get_my_primary_local_ipv4, \
+from python.common.structlog_setup import setup_logging, update_logging
+from python.common.utils.asleep import asleep
+from python.common.utils.deferred_utils import TimeOutError
+from python.common.utils.dockerhelpers import get_my_containers_name
+from python.common.utils.nethelpers import get_my_primary_local_ipv4, \
get_my_primary_interface
-from adapters.common.utils.registry import registry, IComponent
-from adapters.kafka.adapter_proxy import AdapterProxy
-from adapters.kafka.adapter_request_facade import AdapterRequestFacade
-from adapters.kafka.core_proxy import CoreProxy
-from adapters.kafka.kafka_inter_container_library import IKafkaMessagingProxy, \
+from python.common.utils.registry import registry, IComponent
+from python.adapters.kafka.adapter_proxy import AdapterProxy
+from python.adapters.kafka.adapter_request_facade import AdapterRequestFacade
+from python.adapters.kafka.core_proxy import CoreProxy
+from python.adapters.kafka.kafka_inter_container_library import IKafkaMessagingProxy, \
get_messaging_proxy
-from adapters.kafka.kafka_proxy import KafkaProxy, get_kafka_proxy
-from adapters.ponsim_onu.ponsim_onu import PonSimOnuAdapter
-from adapters.protos import third_party
-from adapters.protos.adapter_pb2 import AdapterConfig
+from python.adapters.kafka.kafka_proxy import KafkaProxy, get_kafka_proxy
+from ponsim_onu import PonSimOnuAdapter
+from python.protos import third_party
+from python.protos.adapter_pb2 import AdapterConfig
_ = third_party
diff --git a/python/adapters/ponsim_onu/ponsim_onu.py b/python/adapters/ponsim_onu/ponsim_onu.py
index e15d0a9..eb4d716 100644
--- a/python/adapters/ponsim_onu/ponsim_onu.py
+++ b/python/adapters/ponsim_onu/ponsim_onu.py
@@ -29,20 +29,20 @@
returnValue, Deferred
from twisted.internet.task import LoopingCall
-from adapters.common.utils.asleep import asleep
-from adapters.iadapter import OnuAdapter
-from adapters.kafka.kafka_proxy import get_kafka_proxy
-from adapters.protos import third_party
-from adapters.protos.common_pb2 import OperStatus, ConnectStatus, AdminState
-from adapters.protos.core_adapter_pb2 import PortCapability, \
+from python.common.utils.asleep import asleep
+from python.adapters.iadapter import OnuAdapter
+from python.adapters.kafka.kafka_proxy import get_kafka_proxy
+from python.protos import third_party
+from python.protos.common_pb2 import OperStatus, ConnectStatus, AdminState
+from python.protos.core_adapter_pb2 import PortCapability, \
InterAdapterMessageType, InterAdapterResponseBody
-from adapters.protos.device_pb2 import Port, PmConfig, PmConfigs
-from adapters.protos.events_pb2 import KpiEvent, KpiEventType, MetricValuePairs
-from adapters.protos.logical_device_pb2 import LogicalPort
-from adapters.protos.openflow_13_pb2 import OFPPS_LIVE, OFPPF_FIBER, \
+from python.protos.device_pb2 import Port, PmConfig, PmConfigs
+from python.protos.events_pb2 import KpiEvent, KpiEventType, MetricValuePairs
+from python.protos.logical_device_pb2 import LogicalPort
+from python.protos.openflow_13_pb2 import OFPPS_LIVE, OFPPF_FIBER, \
OFPPF_1GB_FD
-from adapters.protos.openflow_13_pb2 import ofp_port
-from adapters.protos.ponsim_pb2 import FlowTable, PonSimMetricsRequest, PonSimMetrics
+from python.protos.openflow_13_pb2 import ofp_port
+from python.protos.ponsim_pb2 import FlowTable, PonSimMetricsRequest, PonSimMetrics
_ = third_party
log = structlog.get_logger()
diff --git a/python/adapters/requirements.txt b/python/adapters/requirements.txt
deleted file mode 100755
index a0641b2..0000000
--- a/python/adapters/requirements.txt
+++ /dev/null
@@ -1,68 +0,0 @@
-argparse==1.2.1
-arrow==0.10.0
-bitstring==3.1.5
-cmd2==0.7.0
-colorama==0.3.9
-cython==0.24.1
-decorator==4.1.2
-docker-py==1.10.6
-fluent-logger==0.6.0
-grpc==0.3.post19
-grpcio==1.3.5
-grpcio-tools==1.3.5
-hash_ring==1.3.1
-hexdump==3.3
-jinja2==2.8
-jsonpatch==1.16
-kafka_python==1.3.5
-klein==17.10.0
-kubernetes==5.0.0
-netaddr==0.7.19
-networkx==2.0
-nose==1.3.7
-nose-exclude==0.5.0
-nose-testconfig==0.10
-mock==2.0.0
-netifaces==0.10.6
-pcapy==0.11.1
-pep8==1.7.1
-pep8-naming>=0.3.3
-protobuf==3.3.0
-protobuf-to-dict==0.1.0
-pyflakes==1.6.0
-pylint==1.7.6
-#pypcap>=1.1.5
-pyOpenSSL==17.3.0
-PyYAML==3.12
-requests==2.18.4
-scapy==2.3.3
-service-identity==17.0.0
-simplejson==3.12.0
-jsonschema==2.6.0
-six==1.11.0
-structlog==17.2.0
-termcolor==1.1.0
-transitions==0.6.4
-treq==17.8.0
-Twisted==17.9.0
-txaioetcd==0.3.0
-urllib3==1.22
-pyang==1.7.3
-lxml==3.6.4
-nosexcover==1.0.11
-zmq==0.0.0
-pyzmq==16.0.3
-txZMQ==0.8.0
-ncclient==0.5.3
-xmltodict==0.11.0
-dicttoxml==1.7.4
-etcd3==0.7.0
-pyparsing==2.2.0
-packaging==17.1
-
-# python-consul>=0.6.1 we need the pre-released version for now, because 0.6.1 does not
-# yet support Twisted. Once this is released, it will be the 0.6.2 version
-git+https://github.com/cablehead/python-consul.git
-
-# Twisted Python kafka client
-git+https://github.com/ciena/afkak.git
diff --git a/python/cli/README.md b/python/cli/README.md
new file mode 100644
index 0000000..c810df4
--- /dev/null
+++ b/python/cli/README.md
@@ -0,0 +1,14 @@
+## CLI (~/cli)
+
+* Add auto-completion for most common args like device and logical device ids
+* Add consistent argument checking
+* Unify code that retrieves data from gRPC
+* Unify code that prints out data/response, to allow:
+ * Selectable output mode:
+ * JSON
+ * Tabular
+* Organize history per sub context so that in each context the commands
+ entered in that context will show
+* Metaprogramming [BIG ONE]: Make large part of the commands come from annotations embedded in
+ the protobuf files and have corresponding handler auto-generated by protoc
+* Package CLI as docker container, bake it into composition
diff --git a/python/common/frameio/__init__.py b/python/cli/__init__.py
similarity index 100%
rename from python/common/frameio/__init__.py
rename to python/cli/__init__.py
diff --git a/python/cli/alarm_filters.py b/python/cli/alarm_filters.py
new file mode 100644
index 0000000..ed2af32
--- /dev/null
+++ b/python/cli/alarm_filters.py
@@ -0,0 +1,238 @@
+#!/usr/bin/env python
+#
+# Copyright 2017 the original author or authors.
+#
+# 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.
+#
+
+"""
+Alarm filter CLI commands
+"""
+from optparse import make_option, OptionValueError
+
+from cmd2 import Cmd, options
+from google.protobuf.empty_pb2 import Empty
+
+from table import print_pb_list_as_table
+from python.protos import third_party
+from python.protos import voltha_pb2
+from python.protos.events_pb2 import AlarmEventType, AlarmEventSeverity, AlarmEventCategory
+
+_ = third_party
+
+
+class AlarmFiltersCli(Cmd):
+ def __init__(self, get_stub):
+ Cmd.__init__(self)
+ self.get_stub = get_stub
+ self.prompt = '(' + self.colorize(
+ self.colorize('alarm_filters', 'red'), 'bold') + ') '
+
+ def cmdloop(self):
+ self._cmdloop()
+
+ def help_show(self):
+ self.poutput(
+'''
+Display the list of configured filters.
+
+Valid options:
+
+-i FILTER_ID | --filter-id=FILTER_ID Display the filter rules for a specific filter id (OPTIONAL)
+
+'''
+ )
+
+ @options([
+ make_option('-i', '--filter-id', action="store", dest='filter_id')
+ ])
+ def do_show(self, line, opts):
+ stub = self.get_stub()
+
+ if not opts.filter_id:
+ result = stub.ListAlarmFilters(Empty())
+ print_pb_list_as_table("Alarm Filters:", result.filters, {}, self.poutput)
+ else:
+ result = stub.GetAlarmFilter(voltha_pb2.ID(id=opts.filter_id))
+ print_pb_list_as_table("Rules for Filter ID = {}:".format(opts.filter_id),
+ result.rules, {}, self.poutput)
+
+ @staticmethod
+ def construct_rule(raw_rule):
+ rule = dict()
+
+ rule_kv = raw_rule.strip().split(':')
+
+ if len(rule_kv) == 2:
+ rule['key'] = rule_kv[0].lower()
+ rule['value'] = rule_kv[1].lower()
+ else:
+ raise OptionValueError("Error: A rule must be a colon separated key/value pair")
+
+ return rule
+
+ def parse_filter_rules(option, opt_str, value, parser):
+ rules = getattr(parser.values, option.dest)
+ if rules is None:
+ rules = list()
+ rules.append(AlarmFiltersCli.construct_rule(value))
+
+ for arg in parser.rargs:
+ if (arg[:2] == "--" and len(arg) > 2) or (arg[:1] == "-" and len(arg) > 1 and arg[1] != "-"):
+ break
+ else:
+ rules.append(AlarmFiltersCli.construct_rule(arg))
+
+ setattr(parser.values, option.dest, rules)
+ else:
+ raise OptionValueError('Warning: The filter rule option can only be specified once')
+
+ def help_create(self):
+ types = list(
+ k for k, v in
+ AlarmEventType.DESCRIPTOR.enum_values_by_name.items())
+ categories = list(
+ k for k, v in
+ AlarmEventCategory.DESCRIPTOR.enum_values_by_name.items())
+ severities = list(
+ k for k, v in
+ AlarmEventSeverity.DESCRIPTOR.enum_values_by_name.items())
+
+ alarm_types = types
+ alarm_categories = categories
+ alarm_severities = severities
+
+ usage = '''
+Create a new alarm filter.
+
+Valid options:
+
+-r rule:value ... | --filter-rules rule:value ... Specify one or more filter rules as key/value pairs (REQUIRED)
+
+Valid rule keys and expected values:
+
+id : Identifier of an incoming alarm
+type : Type of an incoming alarm {}
+category : Category of an incoming alarm {}
+severity : Severity of an incoming alarm {}
+resource_id : Resource identifier of an incoming alarm
+device_id : Device identifier of an incoming alarm
+
+Example:
+
+# Filter any alarm that matches the following criteria
+
+create -r type:environment severity:indeterminate
+create -r device_id:754f9dcbe4a6
+
+'''.format(alarm_types, alarm_categories, alarm_severities)
+
+ self.poutput(usage)
+
+ @options([
+ make_option('-r', '--filter-rules', help='<key>:<value>...', action="callback",
+ callback=parse_filter_rules, type='string', dest='filter_rules'),
+ ])
+ def do_create(self, line, opts):
+ if opts.filter_rules:
+ stub = self.get_stub()
+ result = stub.CreateAlarmFilter(voltha_pb2.AlarmFilter(rules=opts.filter_rules))
+ print_pb_list_as_table("Rules for Filter ID = {}:".format(result.id),
+ result.rules, {}, self.poutput)
+
+ def help_delete(self):
+ self.poutput(
+'''
+Delete a specific alarm filter entry.
+
+Valid options:
+
+-i FILTER_ID | --filter-id=FILTER_ID Display the filter rules for a specific filter id (REQUIRED)
+
+'''
+ )
+
+ @options([
+ make_option('-i', '--filter-id', action="store", dest='filter_id')
+ ])
+ def do_delete(self, line, opts):
+ if not opts.filter_id:
+ self.poutput(self.colorize('Error: ', 'red') + 'Specify ' + \
+ self.colorize(self.colorize('"filter id"', 'blue'),
+ 'bold') + ' to update')
+ return
+
+ stub = self.get_stub()
+ stub.DeleteAlarmFilter(voltha_pb2.ID(id=opts.filter_id))
+
+ def help_update(self):
+ types = list(
+ k for k, v in
+ AlarmEventType.DESCRIPTOR.enum_values_by_name.items())
+ categories = list(
+ k for k, v in
+ AlarmEventCategory.DESCRIPTOR.enum_values_by_name.items())
+ severities = list(
+ k for k, v in
+ AlarmEventSeverity.DESCRIPTOR.enum_values_by_name.items())
+
+ alarm_types = types
+ alarm_categories = categories
+ alarm_severities = severities
+
+ usage = '''
+Update the filter rules for an existing alarm filter.
+
+Valid options:
+
+-i FILTER_ID | --filter-id=FILTER_ID Indicate the alarm filter identifier to update (REQUIRED)
+-r rule:value ... | --filter-rules rule:value ... Specify one or more filter rules as key/value pairs (REQUIRED)
+
+Valid rule keys and expected values:
+
+id : Identifier of an incoming alarm
+type : Type of an incoming alarm {}
+category : Category of an incoming alarm {}
+severity : Severity of an incoming alarm {}
+resource_id : Resource identifier of an incoming alarm
+device_id : Device identifier of an incoming alarm
+
+Example:
+
+# Filter any alarm that matches the following criteria
+
+update -i 9da115b900bc -r type:environment severity:indeterminate resource_id:1554b0517a07
+
+'''.format(alarm_types, alarm_categories, alarm_severities)
+
+ self.poutput(usage)
+
+ @options([
+ make_option('-r', '--filter-rules', help='<key>:<value>...', action="callback",
+ callback=parse_filter_rules, type='string', dest='filter_rules'),
+ make_option('-i', '--filter-id', action="store", dest='filter_id')
+ ])
+ def do_update(self, line, opts):
+ if not opts.filter_id:
+ self.poutput(self.colorize('Error: ', 'red') + 'Specify ' + \
+ self.colorize(self.colorize('"filter id"', 'blue'),
+ 'bold') + ' to update')
+ return
+
+ if opts.filter_rules:
+ stub = self.get_stub()
+ result = stub.UpdateAlarmFilter(
+ voltha_pb2.AlarmFilter(id=opts.filter_id, rules=opts.filter_rules)
+ )
+ print_pb_list_as_table("Rules for Filter ID = {}:".format(result.id),
+ result.rules, {}, self.poutput)
diff --git a/python/cli/device.py b/python/cli/device.py
new file mode 100644
index 0000000..38ea835
--- /dev/null
+++ b/python/cli/device.py
@@ -0,0 +1,597 @@
+#!/usr/bin/env python
+#
+# Copyright 2016 the original author or authors.
+#
+# 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.
+#
+
+"""
+Device level CLI commands
+"""
+from optparse import make_option
+from cmd2 import Cmd, options
+from simplejson import dumps
+
+from table import print_pb_as_table, print_pb_list_as_table
+from utils import print_flows, pb2dict, enum2name
+from python.protos import third_party
+
+_ = third_party
+from python.protos import voltha_pb2, common_pb2
+import sys
+import json
+from google.protobuf.json_format import MessageToDict
+
+# Since proto3 won't send fields that are set to 0/false/"" any object that
+# might have those values set in them needs to be replicated here such that the
+# fields can be adequately
+
+
+class DeviceCli(Cmd):
+
+ def __init__(self, device_id, get_stub):
+ Cmd.__init__(self)
+ self.get_stub = get_stub
+ self.device_id = device_id
+ self.prompt = '(' + self.colorize(
+ self.colorize('device {}'.format(device_id), 'red'), 'bold') + ') '
+ self.pm_config_last = None
+ self.pm_config_dirty = False
+
+ def cmdloop(self):
+ self._cmdloop()
+
+ def get_device(self, depth=0):
+ stub = self.get_stub()
+ res = stub.GetDevice(voltha_pb2.ID(id=self.device_id),
+ metadata=(('get-depth', str(depth)), ))
+ return res
+
+ do_exit = Cmd.do_quit
+
+ def do_quit(self, line):
+ if self.pm_config_dirty:
+ self.poutput("Uncommited changes for " + \
+ self.colorize(
+ self.colorize("perf_config,", "blue"),
+ "bold") + " please either " + self.colorize(
+ self.colorize("commit", "blue"), "bold") + \
+ " or " + self.colorize(
+ self.colorize("reset", "blue"), "bold") + \
+ " your changes using " + \
+ self.colorize(
+ self.colorize("perf_config", "blue"), "bold"))
+ return False
+ else:
+ return self._STOP_AND_EXIT
+
+ def do_show(self, line):
+ """Show detailed device information"""
+ print_pb_as_table('Device {}'.format(self.device_id),
+ self.get_device(depth=-1))
+
+ def do_ports(self, line):
+ """Show ports of device"""
+ device = self.get_device(depth=-1)
+ omit_fields = {
+ }
+ print_pb_list_as_table('Device ports:', device.ports,
+ omit_fields, self.poutput)
+
+ def complete_perf_config(self, text, line, begidx, endidx):
+ sub_cmds = {"show", "set", "commit", "reset"}
+ sub_opts = {"-f", "-e", "-d", "-o"}
+ # Help the interpreter complete the paramters.
+ completions = []
+ if not self.pm_config_last:
+ device = self.get_device(depth=-1)
+ self.pm_config_last = device.pm_configs
+ m_names = [d.name for d in self.pm_config_last.metrics]
+ cur_cmd = line.strip().split(" ")
+ try:
+ if not text and len(cur_cmd) == 1:
+ completions = ("show", "set", "commit", "reset")
+ elif len(cur_cmd) == 2:
+ if "set" == cur_cmd[1]:
+ completions = [d for d in sub_opts]
+ else:
+ completions = [d for d in sub_cmds if d.startswith(text)]
+ elif len(cur_cmd) > 2 and cur_cmd[1] == "set":
+ if cur_cmd[len(cur_cmd)-1] == "-":
+ completions = [list(d)[1] for d in sub_opts]
+ elif cur_cmd[len(cur_cmd)-1] == "-f":
+ completions = ("\255","Please enter a sampling frequency in 10ths of a second")
+ elif cur_cmd[len(cur_cmd)-2] == "-f":
+ completions = [d for d in sub_opts]
+ elif cur_cmd[len(cur_cmd)-1] in {"-e","-d","-o"}:
+ if self.pm_config_last.grouped:
+ pass
+ else:
+ completions = [d.name for d in self.pm_config_last.metrics]
+ elif cur_cmd[len(cur_cmd)-2] in {"-e","-d"}:
+ if text and text not in m_names:
+ completions = [d for d in m_names if d.startswith(text)]
+ else:
+ completions = [d for d in sub_opts]
+ elif cur_cmd[len(cur_cmd)-2] == "-o":
+ if cur_cmd[len(cur_cmd)-1] in [d.name for d in self.pm_config_last.metrics]:
+ completions = ("\255","Please enter a sampling frequency in 10ths of a second")
+ else:
+ completions = [d for d in m_names if d.startswith(text)]
+ elif cur_cmd[len(cur_cmd)-3] == "-o":
+ completions = [d for d in sub_opts]
+ except:
+ e = sys.exc_info()
+ print(e)
+ return completions
+
+
+ def help_perf_config(self):
+ self.poutput(
+'''
+perf_config [show | set | commit | reset] [-f <default frequency>] [{-e <metric/group
+ name>}] [{-d <metric/group name>}] [{-o <metric/group name> <override
+ frequency>}]
+
+show: displays the performance configuration of the device
+set: changes the parameters specified with -e, -d, and -o
+reset: reverts any changes made since the last commit
+commit: commits any changes made which applies them to the device.
+
+-e: enable collection of the specified metric, more than one -e may be
+ specified.
+-d: disable collection of the specified metric, more than on -d may be
+ specified.
+-o: override the collection frequency of the specified metric, more than one -o
+ may be specified. Note that -o isn't valid unless
+ frequency_override is set to True for the device.
+
+Changes made by set are held locally until a commit or reset command is issued.
+A commit command will write the configuration to the device and it takes effect
+immediately. The reset command will undo any changes since the start of the
+device session.
+
+If grouped is true then the -d, -e and -o commands refer to groups and not
+individual metrics.
+'''
+ )
+
+ @options([
+ make_option('-f', '--default_freq', action="store", dest='default_freq',
+ type='long', default=None),
+ make_option('-e', '--enable', action='append', dest='enable',
+ default=None),
+ make_option('-d', '--disable', action='append', dest='disable',
+ default=None),
+ make_option('-o', '--override', action='append', dest='override',
+ nargs=2, default=None, type='string'),
+ ])
+ def do_perf_config(self, line, opts):
+ """Show and set the performance monitoring configuration of the device"""
+
+ device = self.get_device(depth=-1)
+ if not self.pm_config_last:
+ self.pm_config_last = device.pm_configs
+
+ # Ensure that a valid sub-command was provided
+ if line.strip() not in {"set", "show", "commit", "reset", ""}:
+ self.poutput(self.colorize('Error: ', 'red') +
+ self.colorize(self.colorize(line.strip(), 'blue'),
+ 'bold') + ' is not recognized')
+ return
+
+ # Ensure no options are provided when requesting to view the config
+ if line.strip() == "show" or line.strip() == "":
+ if opts.default_freq or opts.enable or opts.disable:
+ self.poutput(opts.disable)
+ self.poutput(self.colorize('Error: ', 'red') + 'use ' +
+ self.colorize(self.colorize('"set"', 'blue'),
+ 'bold') + ' to change settings')
+ return
+
+ if line.strip() == "set": # Set the supplied values
+ metric_list = set()
+ if opts.enable is not None:
+ metric_list |= {metric for metric in opts.enable}
+ if opts.disable is not None:
+ metric_list |= {metric for metric in opts.disable}
+ if opts.override is not None:
+ metric_list |= {metric for metric, _ in opts.override}
+
+ # The default frequency
+ if opts.default_freq:
+ self.pm_config_last.default_freq = opts.default_freq
+ self.pm_config_dirty = True
+
+ # Field or group visibility
+ if self.pm_config_last.grouped:
+ for g in self.pm_config_last.groups:
+ if opts.enable:
+ if g.group_name in opts.enable:
+ g.enabled = True
+ self.pm_config_dirty = True
+ metric_list.discard(g.group_name)
+ for g in self.pm_config_last.groups:
+ if opts.disable:
+ if g.group_name in opts.disable:
+ g.enabled = False
+ self.pm_config_dirty = True
+ metric_list.discard(g.group_name)
+ else:
+ for m in self.pm_config_last.metrics:
+ if opts.enable:
+ if m.name in opts.enable:
+ m.enabled = True
+ self.pm_config_dirty = True
+ metric_list.discard(m.name)
+ for m in self.pm_config_last.metrics:
+ if opts.disable:
+ if m.name in opts.disable:
+ m.enabled = False
+ self.pm_config_dirty = True
+ metric_list.discard(m.name)
+
+ # Frequency overrides.
+ if opts.override:
+ if self.pm_config_last.freq_override:
+ oo = dict()
+ for o in opts.override:
+ oo[o[0]] = o[1]
+ if self.pm_config_last.grouped:
+ for g in self.pm_config_last.groups:
+ if g.group_name in oo:
+ try:
+ g.group_freq = int(oo[g.group_name])
+ except ValueError:
+ self.poutput(self.colorize('Warning: ',
+ 'yellow') +
+ self.colorize(oo[g.group_name],
+ 'blue') +
+ " is not an integer... ignored")
+ del oo[g.group_name]
+ self.pm_config_dirty = True
+ metric_list.discard(g.group_name)
+ else:
+ for m in self.pm_config_last.metrics:
+ if m.name in oo:
+ try:
+ m.sample_freq = int(oo[m.name])
+ except ValueError:
+ self.poutput(self.colorize('Warning: ',
+ 'yellow') +
+ self.colorize(oo[m.name],
+ 'blue') +
+ " is not an integer... ignored")
+ del oo[m.name]
+ self.pm_config_dirty = True
+ metric_list.discard(m.name)
+
+ # If there's anything left the input was typoed
+ if self.pm_config_last.grouped:
+ field = 'group'
+ else:
+ field = 'metric'
+ for o in oo:
+ self.poutput(self.colorize('Warning: ', 'yellow') +
+ 'the parameter' + ' ' +
+ self.colorize(o, 'blue') + ' is not ' +
+ 'a ' + field + ' name... ignored')
+ if oo:
+ return
+
+ else: # Frequency overrides not enabled
+ self.poutput(self.colorize('Error: ', 'red') +
+ 'Individual overrides are only ' +
+ 'supported if ' +
+ self.colorize('freq_override', 'blue') +
+ ' is set to ' + self.colorize('True', 'blue'))
+ return
+
+ if len(metric_list):
+ metric_name_list = ", ".join(str(metric) for metric in metric_list)
+ self.poutput(self.colorize('Error: ', 'red') +
+ 'Metric/Metric Group{} '.format('s' if len(metric_list) > 1 else '') +
+ self.colorize(metric_name_list, 'blue') +
+ ' {} not found'.format('were' if len(metric_list) > 1 else 'was'))
+ return
+
+ self.poutput("Success")
+ return
+
+ elif line.strip() == "commit" and self.pm_config_dirty:
+ stub = self.get_stub()
+ stub.UpdateDevicePmConfigs(self.pm_config_last)
+ self.pm_config_last = self.get_device(depth=-1).pm_configs
+ self.pm_config_dirty = False
+
+ elif line.strip() == "reset" and self.pm_config_dirty:
+ self.pm_config_last = self.get_device(depth=-1).pm_configs
+ self.pm_config_dirty = False
+
+ omit_fields = {'groups', 'metrics', 'id'}
+ print_pb_as_table('PM Config:', self.pm_config_last, omit_fields,
+ self.poutput,show_nulls=True)
+ if self.pm_config_last.grouped:
+ #self.poutput("Supported metric groups:")
+ for g in self.pm_config_last.groups:
+ if self.pm_config_last.freq_override:
+ omit_fields = {'metrics'}
+ else:
+ omit_fields = {'group_freq','metrics'}
+ print_pb_as_table('', g, omit_fields, self.poutput,
+ show_nulls=True)
+ if g.enabled:
+ state = 'enabled'
+ else:
+ state = 'disabled'
+ print_pb_list_as_table(
+ 'Metric group {} is {}'.format(g.group_name,state),
+ g.metrics, {'enabled', 'sample_freq'}, self.poutput,
+ dividers=100, show_nulls=True)
+ else:
+ if self.pm_config_last.freq_override:
+ omit_fields = {}
+ else:
+ omit_fields = {'sample_freq'}
+ print_pb_list_as_table('Supported metrics:', self.pm_config_last.metrics,
+ omit_fields, self.poutput, dividers=100,
+ show_nulls=True)
+
+ def do_flows(self, line):
+ """Show flow table for device"""
+ device = pb2dict(self.get_device(-1))
+ print_flows(
+ 'Device',
+ self.device_id,
+ type=device['type'],
+ flows=device['flows']['items'],
+ groups=device['flow_groups']['items']
+ )
+
+ def do_images(self, line):
+ """Show software images on the device"""
+ device = self.get_device(depth=-1)
+ omit_fields = {}
+ print_pb_list_as_table('Software Images:', device.images.image,
+ omit_fields, self.poutput, show_nulls=True)
+
+ @options([
+ make_option('-u', '--url', action='store', dest='url',
+ help="URL to get sw image"),
+ make_option('-n', '--name', action='store', dest='name',
+ help="Image name"),
+ make_option('-c', '--crc', action='store', dest='crc',
+ help="CRC code to verify with", default=0),
+ make_option('-v', '--version', action='store', dest='version',
+ help="Image version", default=0),
+ ])
+ def do_img_dnld_request(self, line, opts):
+ """
+ Request image download to a device
+ """
+ device = self.get_device(depth=-1)
+ self.poutput('device_id {}'.format(device.id))
+ self.poutput('name {}'.format(opts.name))
+ self.poutput('url {}'.format(opts.url))
+ self.poutput('crc {}'.format(opts.crc))
+ self.poutput('version {}'.format(opts.version))
+ try:
+ device_id = device.id
+ if device_id and opts.name and opts.url:
+ kw = dict(id=device_id)
+ kw['name'] = opts.name
+ kw['url'] = opts.url
+ else:
+ self.poutput('Device ID and URL are needed')
+ raise Exception('Device ID and URL are needed')
+ except Exception as e:
+ self.poutput('Error request img dnld {}. Error:{}'.format(device_id, e))
+ return
+ kw['crc'] = long(opts.crc)
+ kw['image_version'] = opts.version
+ response = None
+ try:
+ request = voltha_pb2.ImageDownload(**kw)
+ stub = self.get_stub()
+ response = stub.DownloadImage(request)
+ except Exception as e:
+ self.poutput('Error download image {}. Error:{}'.format(kw['id'], e))
+ return
+ name = enum2name(common_pb2.OperationResp,
+ 'OperationReturnCode', response.code)
+ self.poutput('response: {}'.format(name))
+ self.poutput('{}'.format(response))
+
+ @options([
+ make_option('-n', '--name', action='store', dest='name',
+ help="Image name"),
+ ])
+ def do_img_dnld_status(self, line, opts):
+ """
+ Get a image download status
+ """
+ device = self.get_device(depth=-1)
+ self.poutput('device_id {}'.format(device.id))
+ self.poutput('name {}'.format(opts.name))
+ try:
+ device_id = device.id
+ if device_id and opts.name:
+ kw = dict(id=device_id)
+ kw['name'] = opts.name
+ else:
+ self.poutput('Device ID, Image Name are needed')
+ raise Exception('Device ID, Image Name are needed')
+ except Exception as e:
+ self.poutput('Error get img dnld status {}. Error:{}'.format(device_id, e))
+ return
+ status = None
+ try:
+ img_dnld = voltha_pb2.ImageDownload(**kw)
+ stub = self.get_stub()
+ status = stub.GetImageDownloadStatus(img_dnld)
+ except Exception as e:
+ self.poutput('Error get img dnld status {}. Error:{}'.format(device_id, e))
+ return
+ fields_to_omit = {
+ 'crc',
+ 'local_dir',
+ }
+ try:
+ print_pb_as_table('ImageDownload Status:', status, fields_to_omit, self.poutput)
+ except Exception, e:
+ self.poutput('Error {}. Error:{}'.format(device_id, e))
+
+ def do_img_dnld_list(self, line):
+ """
+ List all image download records for a given device
+ """
+ device = self.get_device(depth=-1)
+ device_id = device.id
+ self.poutput('Get all img dnld records {}'.format(device_id))
+ try:
+ stub = self.get_stub()
+ img_dnlds = stub.ListImageDownloads(voltha_pb2.ID(id=device_id))
+ except Exception, e:
+ self.poutput('Error list img dnlds {}. Error:{}'.format(device_id, e))
+ return
+ fields_to_omit = {
+ 'crc',
+ 'local_dir',
+ }
+ try:
+ print_pb_list_as_table('ImageDownloads:', img_dnlds.items, fields_to_omit, self.poutput)
+ except Exception, e:
+ self.poutput('Error {}. Error:{}'.format(device_id, e))
+
+
+ @options([
+ make_option('-n', '--name', action='store', dest='name',
+ help="Image name"),
+ ])
+ def do_img_dnld_cancel(self, line, opts):
+ """
+ Cancel a requested image download
+ """
+ device = self.get_device(depth=-1)
+ self.poutput('device_id {}'.format(device.id))
+ self.poutput('name {}'.format(opts.name))
+ device_id = device.id
+ try:
+ if device_id and opts.name:
+ kw = dict(id=device_id)
+ kw['name'] = opts.name
+ else:
+ self.poutput('Device ID, Image Name are needed')
+ raise Exception('Device ID, Image Name are needed')
+ except Exception as e:
+ self.poutput('Error cancel sw dnld {}. Error:{}'.format(device_id, e))
+ return
+ response = None
+ try:
+ img_dnld = voltha_pb2.ImageDownload(**kw)
+ stub = self.get_stub()
+ img_dnld = stub.GetImageDownload(img_dnld)
+ response = stub.CancelImageDownload(img_dnld)
+ except Exception as e:
+ self.poutput('Error cancel sw dnld {}. Error:{}'.format(device_id, e))
+ return
+ name = enum2name(common_pb2.OperationResp,
+ 'OperationReturnCode', response.code)
+ self.poutput('response: {}'.format(name))
+ self.poutput('{}'.format(response))
+
+ @options([
+ make_option('-n', '--name', action='store', dest='name',
+ help="Image name"),
+ make_option('-s', '--save', action='store', dest='save_config',
+ help="Save Config", default="True"),
+ make_option('-d', '--dir', action='store', dest='local_dir',
+ help="Image on device location"),
+ ])
+ def do_img_activate(self, line, opts):
+ """
+ Activate an image update on device
+ """
+ device = self.get_device(depth=-1)
+ device_id = device.id
+ try:
+ if device_id and opts.name and opts.local_dir:
+ kw = dict(id=device_id)
+ kw['name'] = opts.name
+ kw['local_dir'] = opts.local_dir
+ else:
+ self.poutput('Device ID, Image Name, and Location are needed')
+ raise Exception('Device ID, Image Name, and Location are needed')
+ except Exception as e:
+ self.poutput('Error activate image {}. Error:{}'.format(device_id, e))
+ return
+ kw['save_config'] = json.loads(opts.save_config.lower())
+ self.poutput('activate image update {} {} {} {}'.format( \
+ kw['id'], kw['name'],
+ kw['local_dir'], kw['save_config']))
+ response = None
+ try:
+ img_dnld = voltha_pb2.ImageDownload(**kw)
+ stub = self.get_stub()
+ img_dnld = stub.GetImageDownload(img_dnld)
+ response = stub.ActivateImageUpdate(img_dnld)
+ except Exception as e:
+ self.poutput('Error activate image {}. Error:{}'.format(kw['id'], e))
+ return
+ name = enum2name(common_pb2.OperationResp,
+ 'OperationReturnCode', response.code)
+ self.poutput('response: {}'.format(name))
+ self.poutput('{}'.format(response))
+
+ @options([
+ make_option('-n', '--name', action='store', dest='name',
+ help="Image name"),
+ make_option('-s', '--save', action='store', dest='save_config',
+ help="Save Config", default="True"),
+ make_option('-d', '--dir', action='store', dest='local_dir',
+ help="Image on device location"),
+ ])
+ def do_img_revert(self, line, opts):
+ """
+ Revert an image update on device
+ """
+ device = self.get_device(depth=-1)
+ device_id = device.id
+ try:
+ if device_id and opts.name and opts.local_dir:
+ kw = dict(id=device_id)
+ kw['name'] = opts.name
+ kw['local_dir'] = opts.local_dir
+ else:
+ self.poutput('Device ID, Image Name, and Location are needed')
+ raise Exception('Device ID, Image Name, and Location are needed')
+ except Exception as e:
+ self.poutput('Error revert image {}. Error:{}'.format(device_id, e))
+ return
+ kw['save_config'] = json.loads(opts.save_config.lower())
+ self.poutput('revert image update {} {} {} {}'.format( \
+ kw['id'], kw['name'],
+ kw['local_dir'], kw['save_config']))
+ response = None
+ try:
+ img_dnld = voltha_pb2.ImageDownload(**kw)
+ stub = self.get_stub()
+ img_dnld = stub.GetImageDownload(img_dnld)
+ response = stub.RevertImageUpdate(img_dnld)
+ except Exception as e:
+ self.poutput('Error revert image {}. Error:{}'.format(kw['id'], e))
+ return
+ name = enum2name(common_pb2.OperationResp,
+ 'OperationReturnCode', response.code)
+ self.poutput('response: {}'.format(name))
+ self.poutput('{}'.format(response))
diff --git a/python/cli/logical_device.py b/python/cli/logical_device.py
new file mode 100644
index 0000000..cd991c6
--- /dev/null
+++ b/python/cli/logical_device.py
@@ -0,0 +1,119 @@
+#!/usr/bin/env python
+#
+# Copyright 2016 the original author or authors.
+#
+# 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.
+#
+
+"""
+Logical device level CLI commands
+"""
+from cmd2 import Cmd
+from simplejson import dumps
+
+from table import print_pb_as_table, print_pb_list_as_table
+from utils import pb2dict
+from utils import print_flows, print_groups
+from python.protos import third_party
+from google.protobuf.empty_pb2 import Empty
+
+_ = third_party
+from python.protos import voltha_pb2
+
+
+class LogicalDeviceCli(Cmd):
+
+ def __init__(self, logical_device_id, get_stub):
+ Cmd.__init__(self)
+ self.get_stub = get_stub
+ self.logical_device_id = logical_device_id
+ self.prompt = '(' + self.colorize(
+ self.colorize('logical device {}'.format(logical_device_id), 'red'),
+ 'bold') + ') '
+
+ def cmdloop(self):
+ self._cmdloop()
+
+ def get_logical_device(self, depth=0):
+ stub = self.get_stub()
+ res = stub.GetLogicalDevice(voltha_pb2.ID(id=self.logical_device_id),
+ metadata=(('get-depth', str(depth)), ))
+ return res
+
+ def get_device(self, id):
+ stub = self.get_stub()
+ return stub.GetDevice(voltha_pb2.ID(id=id))
+
+ def get_devices(self):
+ stub = self.get_stub()
+ res = stub.ListDevices(Empty())
+ return res.items
+
+ do_exit = Cmd.do_quit
+
+ def do_show(self, _):
+ """Show detailed logical device information"""
+ print_pb_as_table('Logical device {}'.format(self.logical_device_id),
+ self.get_logical_device(depth=-1))
+
+ def do_ports(self, _):
+ """Show ports of logical device"""
+ device = self.get_logical_device(depth=-1)
+ omit_fields = {
+ 'ofp_port.advertised',
+ 'ofp_port.peer',
+ 'ofp_port.max_speed'
+ }
+ print_pb_list_as_table('Logical device ports:', device.ports,
+ omit_fields, self.poutput)
+
+ def do_flows(self, _):
+ """Show flow table for logical device"""
+ logical_device = pb2dict(self.get_logical_device(-1))
+ print_flows(
+ 'Logical Device',
+ self.logical_device_id,
+ type='n/a',
+ flows=logical_device['flows']['items'],
+ groups=logical_device['flow_groups']['items']
+ )
+
+ def do_groups(self, _):
+ """Show flow group table for logical device"""
+ logical_device = pb2dict(self.get_logical_device(-1))
+ print_groups(
+ 'Logical Device',
+ self.logical_device_id,
+ type='n/a',
+ groups=logical_device['flow_groups']['items']
+ )
+
+ def do_devices(self, line):
+ """List devices that belong to this logical device"""
+ logical_device = self.get_logical_device()
+ root_device_id = logical_device.root_device_id
+ devices = [self.get_device(root_device_id)]
+ for d in self.get_devices():
+ if d.parent_id == root_device_id:
+ devices.append(d)
+ omit_fields = {
+ 'adapter',
+ 'vendor',
+ 'model',
+ 'hardware_version',
+ 'software_version',
+ 'firmware_version',
+ 'serial_number'
+ }
+ print_pb_list_as_table('Devices:', devices, omit_fields, self.poutput)
+
diff --git a/python/cli/main.py b/python/cli/main.py
new file mode 100755
index 0000000..0348f66
--- /dev/null
+++ b/python/cli/main.py
@@ -0,0 +1,922 @@
+#!/usr/bin/env python
+#
+# Copyright 2016 the original author or authors.
+#
+# 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.
+#
+import argparse
+import os
+import readline
+import sys
+from optparse import make_option
+from time import sleep, time
+
+import grpc
+import requests
+from cmd2 import Cmd, options
+from consul import Consul
+from google.protobuf.empty_pb2 import Empty
+from simplejson import dumps
+
+from device import DeviceCli
+from omci import OmciCli
+from alarm_filters import AlarmFiltersCli
+from logical_device import LogicalDeviceCli
+from table import print_pb_list_as_table
+from python.common.openflow.utils import *
+from python.protos import third_party
+from python.protos import voltha_pb2
+from python.protos.openflow_13_pb2 import FlowTableUpdate, FlowGroupTableUpdate
+
+_ = third_party
+from python.cli.utils import pb2dict
+
+defs = dict(
+ # config=os.environ.get('CONFIG', './cli.yml'),
+ consul=os.environ.get('CONSUL', 'localhost:8500'),
+ voltha_grpc_endpoint=os.environ.get('VOLTHA_GRPC_ENDPOINT',
+ 'localhost:50057'),
+ voltha_sim_rest_endpoint=os.environ.get('VOLTHA_SIM_REST_ENDPOINT',
+ 'localhost:18880'),
+ global_request=os.environ.get('GLOBAL_REQUEST', False)
+)
+
+banner = """\
+ _ _ _ ___ _ ___
+__ _____| | |_| |_ __ _ / __| | |_ _|
+\ V / _ \ | _| ' \/ _` | | (__| |__ | |
+ \_/\___/_|\__|_||_\__,_| \___|____|___|
+(to exit type quit or hit Ctrl-D)
+"""
+
+
+class VolthaCli(Cmd):
+ prompt = 'voltha'
+ history_file_name = '.voltha_cli_history'
+
+ # Settable CLI parameters
+ voltha_grpc = 'localhost:50057'
+ voltha_sim_rest = 'localhost:18880'
+ global_request = False
+ max_history_lines = 500
+ default_device_id = None
+ default_logical_device_id = None
+
+ Cmd.settable.update(dict(
+ voltha_grpc='Voltha GRPC endpoint in form of <host>:<port>',
+ voltha_sim_rest='Voltha simulation back door for testing in form '
+ 'of <host>:<port>',
+ max_history_lines='Maximum number of history lines stored across '
+ 'sessions',
+ default_device_id='Device id used when no device id is specified',
+ default_logical_device_id='Logical device id used when no device id '
+ 'is specified',
+ ))
+
+ # cleanup of superfluous commands from cmd2
+ del Cmd.do_cmdenvironment
+ del Cmd.do_load
+ del Cmd.do__relative_load
+
+ def __init__(self, voltha_grpc, voltha_sim_rest, global_request=False):
+
+ VolthaCli.voltha_grpc = "localhost:50057"
+ # VolthaCli.voltha_grpc = voltha_grpc
+ VolthaCli.voltha_sim_rest = voltha_sim_rest
+ VolthaCli.global_request = global_request
+ Cmd.__init__(self)
+ self.prompt = '(' + self.colorize(
+ self.colorize(self.prompt, 'blue'), 'bold') + ') '
+ self.channel = None
+ self.stub = None
+ self.device_ids_cache = None
+ self.device_ids_cache_ts = time()
+ self.logical_device_ids_cache = None
+ self.logical_device_ids_cache_ts = time()
+
+ # we override cmd2's method to avoid its optparse conflicting with our
+ # command line parsing
+ def cmdloop(self):
+ self._cmdloop()
+
+ def load_history(self):
+ """Load saved command history from local history file"""
+ try:
+ with file(self.history_file_name, 'r') as f:
+ for line in f.readlines():
+ stripped_line = line.strip()
+ self.history.append(stripped_line)
+ readline.add_history(stripped_line)
+ except IOError:
+ pass # ignore if file cannot be read
+
+ def save_history(self):
+ try:
+ with open(self.history_file_name, 'w') as f:
+ f.write('\n'.join(self.history[-self.max_history_lines:]))
+ except IOError as e:
+ self.perror('Could not save history in {}: {}'.format(
+ self.history_file_name, e))
+ else:
+ self.poutput('History saved as {}'.format(
+ self.history_file_name))
+
+ def perror(self, errmsg, statement=None):
+ # Touch it up to make sure error is prefixed and colored
+ Cmd.perror(self, self.colorize('***ERROR: ', 'red') + errmsg,
+ statement)
+
+ def get_channel(self):
+ if self.channel is None:
+ self.channel = grpc.insecure_channel(self.voltha_grpc)
+ return self.channel
+
+ def get_stub(self):
+ if self.stub is None:
+ self.stub = voltha_pb2.VolthaServiceStub(self.get_channel())
+ # self.stub = \
+ # voltha_pb2.VolthaGlobalServiceStub(self.get_channel()) \
+ # if self.global_request else \
+ # voltha_pb2.VolthaLocalServiceStub(self.get_channel())
+ return self.stub
+
+ # ~~~~~~~~~~~~~~~~~ ACTUAL COMMAND IMPLEMENTATIONS ~~~~~~~~~~~~~~~~~~~~~~~~
+
+ def do_reset_history(self, line):
+ """Reset CLI history"""
+ while self.history:
+ self.history.pop()
+
+ def do_launch(self, line):
+ """If Voltha is not running yet, launch it"""
+ raise NotImplementedError('not implemented yet')
+
+ def do_restart(self, line):
+ """Launch Voltha, but if it is already running, terminate it first"""
+ pass
+
+ def do_adapters(self, line):
+ """List loaded adapter"""
+ stub = self.get_stub()
+ res = stub.ListAdapters(Empty())
+ omit_fields = {'config.log_level', 'logical_device_ids'}
+ print_pb_list_as_table('Adapters:', res.items, omit_fields, self.poutput)
+
+ def get_devices(self):
+ stub = self.get_stub()
+ res = stub.ListDevices(Empty())
+ return res.items
+
+ def get_logical_devices(self):
+ stub = self.get_stub()
+ res = stub.ListLogicalDevices(Empty())
+ return res.items
+
+ def do_devices(self, line):
+ """List devices registered in Voltha"""
+ devices = self.get_devices()
+ omit_fields = {
+ 'adapter',
+ 'vendor',
+ 'model',
+ 'hardware_version',
+ 'images',
+ 'firmware_version',
+ 'vendor_id'
+ }
+ print_pb_list_as_table('Devices:', devices, omit_fields, self.poutput)
+
+ def do_logical_devices(self, line):
+ """List logical devices in Voltha"""
+ stub = self.get_stub()
+ res = stub.ListLogicalDevices(Empty())
+ omit_fields = {
+ 'desc.mfr_desc',
+ 'desc.hw_desc',
+ 'desc.sw_desc',
+ 'desc.dp_desc',
+ 'desc.serial_number',
+ 'switch_features.capabilities'
+ }
+ presfns = {
+ 'datapath_id': lambda x: "{0:0{1}x}".format(int(x), 16)
+ }
+ print_pb_list_as_table('Logical devices:', res.items, omit_fields,
+ self.poutput, presfns=presfns)
+
+ def do_device(self, line):
+ """Enter device level command mode"""
+ device_id = line.strip() or self.default_device_id
+ if not device_id:
+ raise Exception('<device-id> parameter needed')
+ if device_id not in self.device_ids():
+ self.poutput( self.colorize('Error: ', 'red') +
+ 'There is no such device')
+ raise Exception('<device-id> is not a valid one')
+ sub = DeviceCli(device_id, self.get_stub)
+ sub.cmdloop()
+
+ def do_logical_device(self, line):
+ """Enter logical device level command mode"""
+ logical_device_id = line.strip() or self.default_logical_device_id
+ if not logical_device_id:
+ raise Exception('<logical-device-id> parameter needed')
+ if logical_device_id not in self.logical_device_ids():
+ self.poutput( self.colorize('Error: ', 'red') +
+ 'There is no such device')
+ raise Exception('<logical-device-id> is not a valid one')
+ sub = LogicalDeviceCli(logical_device_id, self.get_stub)
+ sub.cmdloop()
+
+ def device_ids(self, force_refresh=False):
+ if force_refresh or self.device_ids is None or \
+ (time() - self.device_ids_cache_ts) > 1:
+ self.device_ids_cache = [d.id for d in self.get_devices()]
+ self.device_ids_cache_ts = time()
+ return self.device_ids_cache
+
+ def logical_device_ids(self, force_refresh=False):
+ if force_refresh or self.logical_device_ids is None or \
+ (time() - self.logical_device_ids_cache_ts) > 1:
+ self.logical_device_ids_cache = [d.id for d
+ in self.get_logical_devices()]
+ self.logical_device_ids_cache_ts = time()
+ return self.logical_device_ids_cache
+
+ def complete_device(self, text, line, begidx, endidx):
+ if not text:
+ completions = self.device_ids()[:]
+ else:
+ completions = [d for d in self.device_ids() if d.startswith(text)]
+ return completions
+
+ def complete_logical_device(self, text, line, begidx, endidx):
+ if not text:
+ completions = self.logical_device_ids()[:]
+ else:
+ completions = [d for d in self.logical_device_ids()
+ if d.startswith(text)]
+ return completions
+
+ def do_xpon(self, line):
+ """xpon <optional> [device_ID] - Enter xpon level command mode"""
+ device_id = line.strip()
+ if device_id:
+ stub = self.get_stub()
+ try:
+ res = stub.GetDevice(voltha_pb2.ID(id=device_id))
+ except Exception:
+ self.poutput(
+ self.colorize('Error: ', 'red') + 'No device id ' +
+ self.colorize(device_id, 'blue') + ' is found')
+ return
+ sub = XponCli(self.get_channel, device_id)
+ sub.cmdloop()
+
+ def do_omci(self, line):
+ """omci <device_ID> - Enter OMCI level command mode"""
+
+ device_id = line.strip() or self.default_device_id
+ if not device_id:
+ raise Exception('<device-id> parameter needed')
+ sub = OmciCli(device_id, self.get_stub)
+ sub.cmdloop()
+
+ def do_pdb(self, line):
+ """Launch PDB debug prompt in CLI (for CLI development)"""
+ from pdb import set_trace
+ set_trace()
+
+ def do_version(self, line):
+ """Show the VOLTHA core version"""
+ stub = self.get_stub()
+ voltha = stub.GetVoltha(Empty())
+ self.poutput('{}'.format(voltha.version))
+
+ def do_health(self, line):
+ """Show connectivity status to Voltha status"""
+ stub = voltha_pb2.HealthServiceStub(self.get_channel())
+ res = stub.GetHealthStatus(Empty())
+ self.poutput(dumps(pb2dict(res), indent=4))
+
+ @options([
+ make_option('-t', '--device-type', action="store", dest='device_type',
+ help="Device type", default='simulated_olt'),
+ make_option('-m', '--mac-address', action='store', dest='mac_address',
+ default='00:0c:e2:31:40:00'),
+ make_option('-i', '--ip-address', action='store', dest='ip_address'),
+ make_option('-H', '--host_and_port', action='store',
+ dest='host_and_port'),
+ ])
+ def do_preprovision_olt(self, line, opts):
+ """Preprovision a new OLT with given device type"""
+ stub = self.get_stub()
+ kw = dict(type=opts.device_type)
+ if opts.host_and_port:
+ kw['host_and_port'] = opts.host_and_port
+ elif opts.ip_address:
+ kw['ipv4_address'] = opts.ip_address
+ elif opts.mac_address:
+ kw['mac_address'] = opts.mac_address.lower()
+ else:
+ raise Exception('Either IP address or Mac Address is needed')
+ # Pass any extra arguments past '--' to the device as custom arguments
+ kw['extra_args'] = line
+
+ device = voltha_pb2.Device(**kw)
+ device = stub.CreateDevice(device)
+ self.poutput('success (device id = {})'.format(device.id))
+ self.default_device_id = device.id
+
+ def do_enable(self, line):
+ """
+ Enable a device. If the <id> is not provided, it will be on the last
+ pre-provisioned device.
+ """
+ device_id = line or self.default_device_id
+ if device_id not in self.device_ids():
+ self.poutput('Error: There is no such preprovisioned device')
+ return
+
+ try:
+ stub = self.get_stub()
+ device = stub.GetDevice(voltha_pb2.ID(id=device_id))
+ if device.admin_state == voltha_pb2.AdminState.ENABLED:
+ if device.oper_status != voltha_pb2.OperStatus.ACTIVATING:
+ self.poutput('Error: Device is already enabled')
+ return
+ else:
+ stub.EnableDevice(voltha_pb2.ID(id=device_id))
+ self.poutput('enabling {}'.format(device_id))
+
+ while True:
+ device = stub.GetDevice(voltha_pb2.ID(id=device_id))
+ # If this is an OLT then acquire logical device id
+ if device.oper_status == voltha_pb2.OperStatus.ACTIVE:
+ if device.type.endswith('_olt'):
+ assert device.parent_id
+ self.default_logical_device_id = device.parent_id
+ self.poutput('success (logical device id = {})'.format(
+ self.default_logical_device_id))
+ else:
+ self.poutput('success (device id = {})'.format(device.id))
+ break
+ self.poutput('waiting for device to be enabled...')
+ sleep(.5)
+ except Exception as e:
+ self.poutput('Error enabling {}. Error:{}'.format(device_id, e))
+
+ complete_activate_olt = complete_device
+
+ def do_reboot(self, line):
+ """
+ Rebooting a device. ID of the device needs to be provided
+ """
+ device_id = line or self.default_device_id
+ self.poutput('rebooting {}'.format(device_id))
+ try:
+ stub = self.get_stub()
+ stub.RebootDevice(voltha_pb2.ID(id=device_id))
+ self.poutput('rebooted {}'.format(device_id))
+ except Exception as e:
+ self.poutput('Error rebooting {}. Error:{}'.format(device_id, e))
+
+ def do_self_test(self, line):
+ """
+ Self Test a device. ID of the device needs to be provided
+ """
+ device_id = line or self.default_device_id
+ self.poutput('Self Testing {}'.format(device_id))
+ try:
+ stub = self.get_stub()
+ res = stub.SelfTest(voltha_pb2.ID(id=device_id))
+ self.poutput('Self Tested {}'.format(device_id))
+ self.poutput(dumps(pb2dict(res), indent=4))
+ except Exception as e:
+ self.poutput('Error in self test {}. Error:{}'.format(device_id, e))
+
+ def do_delete(self, line):
+ """
+ Deleting a device. ID of the device needs to be provided
+ """
+ device_id = line or self.default_device_id
+ self.poutput('deleting {}'.format(device_id))
+ try:
+ stub = self.get_stub()
+ stub.DeleteDevice(voltha_pb2.ID(id=device_id))
+ self.poutput('deleted {}'.format(device_id))
+ except Exception as e:
+ self.poutput('Error deleting {}. Error:{}'.format(device_id, e))
+
+ def do_disable(self, line):
+ """
+ Disable a device. ID of the device needs to be provided
+ """
+ device_id = line
+ if device_id not in self.device_ids():
+ self.poutput('Error: There is no such device')
+ return
+ try:
+ stub = self.get_stub()
+ device = stub.GetDevice(voltha_pb2.ID(id=device_id))
+ if device.admin_state == voltha_pb2.AdminState.DISABLED:
+ self.poutput('Error: Device is already disabled')
+ return
+ stub.DisableDevice(voltha_pb2.ID(id=device_id))
+ self.poutput('disabling {}'.format(device_id))
+
+ # Do device query and verify that the device admin status is
+ # DISABLED and Operational Status is unknown
+ device = stub.GetDevice(voltha_pb2.ID(id=device_id))
+ if device.admin_state == voltha_pb2.AdminState.DISABLED:
+ self.poutput('disabled successfully {}'.format(device_id))
+ else:
+ self.poutput('disabling failed {}. Admin State:{} '
+ 'Operation State: {}'.format(device_id,
+ device.admin_state,
+ device.oper_status))
+ except Exception as e:
+ self.poutput('Error disabling {}. Error:{}'.format(device_id, e))
+
+ def do_test(self, line):
+ """Enter test mode, which makes a bunch on new commands available"""
+ sub = TestCli(self.history, self.voltha_grpc,
+ self.get_stub, self.voltha_sim_rest)
+ sub.cmdloop()
+
+ def do_alarm_filters(self, line):
+ sub = AlarmFiltersCli(self.get_stub)
+ sub.cmdloop()
+
+
+class TestCli(VolthaCli):
+ def __init__(self, history, voltha_grpc, get_stub, voltha_sim_rest):
+ VolthaCli.__init__(self, voltha_grpc, voltha_sim_rest)
+ self.history = history
+ self.get_stub = get_stub
+ self.prompt = '(' + self.colorize(self.colorize('test', 'cyan'),
+ 'bold') + ') '
+
+ def get_device(self, device_id, depth=0):
+ stub = self.get_stub()
+ res = stub.GetDevice(voltha_pb2.ID(id=device_id),
+ metadata=(('get-depth', str(depth)),))
+ return res
+
+ def do_arrive_onus(self, line):
+ """
+ Simulate the arrival of ONUs (available only on simulated_olt)
+ """
+ device_id = line or self.default_device_id
+
+ # verify that device is of type simulated_olt
+ device = self.get_device(device_id)
+ assert device.type == 'simulated_olt', (
+ 'Cannot use it on this device type (only on simulated_olt type)')
+
+ requests.get('http://{}/devices/{}/detect_onus'.format(
+ self.voltha_sim_rest, device_id
+ ))
+
+ complete_arrive_onus = VolthaCli.complete_device
+
+ def get_logical_ports(self, logical_device_id):
+ """
+ Return the NNI port number and the first usable UNI port of logical
+ device, and the vlan associated with the latter.
+ """
+ stub = self.get_stub()
+ ports = stub.ListLogicalDevicePorts(
+ voltha_pb2.ID(id=logical_device_id)).items
+ nni = None
+ unis = []
+ for port in ports:
+ if port.root_port:
+ assert nni is None, "There shall be only one root port"
+ nni = port.ofp_port.port_no
+ else:
+ uni = port.ofp_port.port_no
+ uni_device = self.get_device(port.device_id)
+ vlan = uni_device.vlan
+ unis.append((uni, vlan))
+
+ assert nni is not None, "No NNI port found"
+ assert unis, "Not a single UNI?"
+
+ return nni, unis
+
+ def do_install_eapol_flow(self, line):
+ """
+ Install an EAPOL flow on the given logical device. If device is not
+ given, it will be applied to logical device of the last pre-provisioned
+ OLT device.
+ """
+
+ logical_device_id = line or self.default_logical_device_id
+
+ # gather NNI and UNI port IDs
+ nni_port_no, unis = self.get_logical_ports(logical_device_id)
+
+ # construct and push flow rule
+ stub = self.get_stub()
+ print "I am now here", unis
+ for uni_port_no, _ in unis:
+ update = FlowTableUpdate(
+ id=logical_device_id,
+ flow_mod=mk_simple_flow_mod(
+ priority=2000,
+ match_fields=[in_port(uni_port_no), eth_type(0x888e)],
+ actions=[
+ # push_vlan(0x8100),
+ # set_field(vlan_vid(4096 + 4000)),
+ output(ofp.OFPP_CONTROLLER)
+ ]
+ )
+ )
+ print "I am now here"
+ res = stub.UpdateLogicalDeviceFlowTable(update)
+ self.poutput('success for uni {} ({})'.format(uni_port_no, res))
+
+ complete_install_eapol_flow = VolthaCli.complete_logical_device
+
+ def do_install_all_controller_bound_flows(self, line):
+ """
+ Install all flow rules for controller bound flows, including EAPOL,
+ IGMP and DHCP. If device is not given, it will be applied to logical
+ device of the last pre-provisioned OLT device.
+ """
+ logical_device_id = line or self.default_logical_device_id
+
+ # gather NNI and UNI port IDs
+ nni_port_no, unis = self.get_logical_ports(logical_device_id)
+
+ # construct and push flow rules
+ stub = self.get_stub()
+
+ for uni_port_no, _ in unis:
+ stub.UpdateLogicalDeviceFlowTable(FlowTableUpdate(
+ id=logical_device_id,
+ flow_mod=mk_simple_flow_mod(
+ priority=2000,
+ match_fields=[
+ in_port(uni_port_no),
+ eth_type(0x888e)
+ ],
+ actions=[output(ofp.OFPP_CONTROLLER)]
+ )
+ ))
+ stub.UpdateLogicalDeviceFlowTable(FlowTableUpdate(
+ id=logical_device_id,
+ flow_mod=mk_simple_flow_mod(
+ priority=1000,
+ match_fields=[
+ in_port(uni_port_no),
+ eth_type(0x800),
+ ip_proto(2)
+ ],
+ actions=[output(ofp.OFPP_CONTROLLER)]
+ )
+ ))
+ stub.UpdateLogicalDeviceFlowTable(FlowTableUpdate(
+ id=logical_device_id,
+ flow_mod=mk_simple_flow_mod(
+ priority=1000,
+ match_fields=[
+ in_port(uni_port_no),
+ eth_type(0x800),
+ ip_proto(17),
+ udp_dst(67)
+ ],
+ actions=[output(ofp.OFPP_CONTROLLER)]
+ )
+ ))
+ self.poutput('success')
+
+ complete_install_all_controller_bound_flows = \
+ VolthaCli.complete_logical_device
+
+ def do_install_all_sample_flows(self, line):
+ """
+ Install all flows that are representative of the virtualized access
+ scenario in a PON network.
+ """
+ logical_device_id = line or self.default_logical_device_id
+
+ # gather NNI and UNI port IDs
+ nni_port_no, unis = self.get_logical_ports(logical_device_id)
+
+ # construct and push flow rules
+ stub = self.get_stub()
+
+ for uni_port_no, c_vid in unis:
+ # Controller-bound flows
+ stub.UpdateLogicalDeviceFlowTable(FlowTableUpdate(
+ id=logical_device_id,
+ flow_mod=mk_simple_flow_mod(
+ priority=2000,
+ match_fields=[in_port(uni_port_no), eth_type(0x888e)],
+ actions=[
+ # push_vlan(0x8100),
+ # set_field(vlan_vid(4096 + 4000)),
+ output(ofp.OFPP_CONTROLLER)
+ ]
+ )
+ ))
+ stub.UpdateLogicalDeviceFlowTable(FlowTableUpdate(
+ id=logical_device_id,
+ flow_mod=mk_simple_flow_mod(
+ priority=1000,
+ match_fields=[eth_type(0x800), ip_proto(2)],
+ actions=[output(ofp.OFPP_CONTROLLER)]
+ )
+ ))
+ stub.UpdateLogicalDeviceFlowTable(FlowTableUpdate(
+ id=logical_device_id,
+ flow_mod=mk_simple_flow_mod(
+ priority=1000,
+ match_fields=[eth_type(0x800), ip_proto(17), udp_dst(67)],
+ actions=[output(ofp.OFPP_CONTROLLER)]
+ )
+ ))
+
+ # Unicast flows:
+ # Downstream flow 1
+ stub.UpdateLogicalDeviceFlowTable(FlowTableUpdate(
+ id=logical_device_id,
+ flow_mod=mk_simple_flow_mod(
+ priority=500,
+ match_fields=[
+ in_port(nni_port_no),
+ vlan_vid(4096 + 1000),
+ metadata(c_vid) # here to mimic an ONOS artifact
+ ],
+ actions=[pop_vlan()],
+ next_table_id=1
+ )
+ ))
+ # Downstream flow 2
+ stub.UpdateLogicalDeviceFlowTable(FlowTableUpdate(
+ id=logical_device_id,
+ flow_mod=mk_simple_flow_mod(
+ priority=500,
+ table_id=1,
+ match_fields=[in_port(nni_port_no), vlan_vid(4096 + c_vid)],
+ actions=[set_field(vlan_vid(4096 + 0)), output(uni_port_no)]
+ )
+ ))
+ # Upstream flow 1 for 0-tagged case
+ stub.UpdateLogicalDeviceFlowTable(FlowTableUpdate(
+ id=logical_device_id,
+ flow_mod=mk_simple_flow_mod(
+ priority=500,
+ match_fields=[in_port(uni_port_no), vlan_vid(4096 + 0)],
+ actions=[set_field(vlan_vid(4096 + c_vid))],
+ next_table_id=1
+ )
+ ))
+ # Upstream flow 1 for untagged case
+ stub.UpdateLogicalDeviceFlowTable(FlowTableUpdate(
+ id=logical_device_id,
+ flow_mod=mk_simple_flow_mod(
+ priority=500,
+ match_fields=[in_port(uni_port_no), vlan_vid(0)],
+ actions=[push_vlan(0x8100), set_field(vlan_vid(4096 + c_vid))],
+ next_table_id=1
+ )
+ ))
+ # Upstream flow 2 for s-tag
+ stub.UpdateLogicalDeviceFlowTable(FlowTableUpdate(
+ id=logical_device_id,
+ flow_mod=mk_simple_flow_mod(
+ priority=500,
+ table_id=1,
+ match_fields=[in_port(uni_port_no), vlan_vid(4096 + c_vid)],
+ actions=[
+ push_vlan(0x8100),
+ set_field(vlan_vid(4096 + 1000)),
+ output(nni_port_no)
+ ]
+ )
+ ))
+
+ # Push a few multicast flows
+ # 1st with one bucket for our uni 0
+ stub.UpdateLogicalDeviceFlowGroupTable(FlowGroupTableUpdate(
+ id=logical_device_id,
+ group_mod=mk_multicast_group_mod(
+ group_id=1,
+ buckets=[
+ ofp.ofp_bucket(actions=[
+ pop_vlan(),
+ output(unis[0][0])
+ ])
+ ]
+ )
+ ))
+ stub.UpdateLogicalDeviceFlowTable(FlowTableUpdate(
+ id=logical_device_id,
+ flow_mod=mk_simple_flow_mod(
+ priority=1000,
+ match_fields=[
+ in_port(nni_port_no),
+ eth_type(0x800),
+ vlan_vid(4096 + 140),
+ ipv4_dst(0xe4010101)
+ ],
+ actions=[group(1)]
+ )
+ ))
+
+ # 2nd with one bucket for uni 0 and 1
+ stub.UpdateLogicalDeviceFlowGroupTable(FlowGroupTableUpdate(
+ id=logical_device_id,
+ group_mod=mk_multicast_group_mod(
+ group_id=2,
+ buckets=[
+ ofp.ofp_bucket(actions=[pop_vlan(), output(unis[0][0])])
+ # ofp.ofp_bucket(actions=[pop_vlan(), output(unis[1][0])])
+ ]
+ )
+ ))
+ stub.UpdateLogicalDeviceFlowTable(FlowTableUpdate(
+ id=logical_device_id,
+ flow_mod=mk_simple_flow_mod(
+ priority=1000,
+ match_fields=[
+ in_port(nni_port_no),
+ eth_type(0x800),
+ vlan_vid(4096 + 140),
+ ipv4_dst(0xe4020202)
+ ],
+ actions=[group(2)]
+ )
+ ))
+
+ # 3rd with empty bucket
+ stub.UpdateLogicalDeviceFlowGroupTable(FlowGroupTableUpdate(
+ id=logical_device_id,
+ group_mod=mk_multicast_group_mod(
+ group_id=3,
+ buckets=[]
+ )
+ ))
+ stub.UpdateLogicalDeviceFlowTable(FlowTableUpdate(
+ id=logical_device_id,
+ flow_mod=mk_simple_flow_mod(
+ priority=1000,
+ match_fields=[
+ in_port(nni_port_no),
+ eth_type(0x800),
+ vlan_vid(4096 + 140),
+ ipv4_dst(0xe4030303)
+ ],
+ actions=[group(3)]
+ )
+ ))
+
+ self.poutput('success')
+
+ complete_install_all_sample_flows = VolthaCli.complete_logical_device
+
+ def do_install_dhcp_flows(self, line):
+ """
+ Install all dhcp flows that are representative of the virtualized access
+ scenario in a PON network.
+ """
+ logical_device_id = line or self.default_logical_device_id
+
+ # gather NNI and UNI port IDs
+ nni_port_no, unis = self.get_logical_ports(logical_device_id)
+
+ # construct and push flow rules
+ stub = self.get_stub()
+
+ # Controller-bound flows
+ for uni_port_no, _ in unis:
+ stub.UpdateLogicalDeviceFlowTable(FlowTableUpdate(
+ id=logical_device_id,
+ flow_mod=mk_simple_flow_mod(
+ priority=1000,
+ match_fields=[
+ in_port(uni_port_no),
+ eth_type(0x800),
+ ip_proto(17),
+ udp_dst(67)
+ ],
+ actions=[output(ofp.OFPP_CONTROLLER)]
+ )
+ ))
+
+ self.poutput('success')
+
+ complete_install_dhcp_flows = VolthaCli.complete_logical_device
+
+ def do_delete_all_flows(self, line):
+ """
+ Remove all flows and flow groups from given logical device
+ """
+ logical_device_id = line or self.default_logical_device_id
+ stub = self.get_stub()
+ stub.UpdateLogicalDeviceFlowTable(FlowTableUpdate(
+ id=logical_device_id,
+ flow_mod=ofp.ofp_flow_mod(
+ command=ofp.OFPFC_DELETE,
+ table_id=ofp.OFPTT_ALL,
+ cookie_mask=0,
+ out_port=ofp.OFPP_ANY,
+ out_group=ofp.OFPG_ANY
+ )
+ ))
+ stub.UpdateLogicalDeviceFlowGroupTable(FlowGroupTableUpdate(
+ id=logical_device_id,
+ group_mod=ofp.ofp_group_mod(
+ command=ofp.OFPGC_DELETE,
+ group_id=ofp.OFPG_ALL
+ )
+ ))
+ self.poutput('success')
+
+ complete_delete_all_flows = VolthaCli.complete_logical_device
+
+ def do_send_simulated_upstream_eapol(self, line):
+ """
+ Send an EAPOL upstream from a simulated OLT
+ """
+ device_id = line or self.default_device_id
+ requests.get('http://{}/devices/{}/test_eapol_in'.format(
+ self.voltha_sim_rest, device_id
+ ))
+
+ complete_send_simulated_upstream_eapol = VolthaCli.complete_device
+
+ def do_inject_eapol_start(self, line):
+ """
+ Send out an an EAPOL start message into the given Unix interface
+ """
+ pass
+
+
+if __name__ == '__main__':
+
+ parser = argparse.ArgumentParser()
+
+ _help = '<hostname>:<port> to consul agent (default: %s)' % defs['consul']
+ parser.add_argument(
+ '-C', '--consul', action='store', default=defs['consul'], help=_help)
+
+ _help = 'Lookup Voltha endpoints based on service entries in Consul'
+ parser.add_argument(
+ '-L', '--lookup', action='store_true', help=_help)
+
+ _help = 'All requests to the Voltha gRPC service are global'
+ parser.add_argument(
+ '-G', '--global_request', action='store_true', help=_help)
+
+ _help = '<hostname>:<port> of Voltha gRPC service (default={})'.format(
+ defs['voltha_grpc_endpoint'])
+ parser.add_argument('-g', '--grpc-endpoint', action='store',
+ default=defs['voltha_grpc_endpoint'], help=_help)
+
+ _help = '<hostname>:<port> of Voltha simulated adapter backend for ' \
+ 'testing (default={})'.format(
+ defs['voltha_sim_rest_endpoint'])
+ parser.add_argument('-s', '--sim-rest-endpoint', action='store',
+ default=defs['voltha_sim_rest_endpoint'], help=_help)
+
+ args = parser.parse_args()
+
+ if args.lookup:
+ host = args.consul.split(':')[0].strip()
+ port = int(args.consul.split(':')[1].strip())
+ consul = Consul(host=host, port=port)
+
+ _, services = consul.catalog.service('voltha-grpc')
+ if not services:
+ print('No voltha-grpc service registered in consul; exiting')
+ sys.exit(1)
+ args.grpc_endpoint = '{}:{}'.format(services[0]['ServiceAddress'],
+ services[0]['ServicePort'])
+
+ _, services = consul.catalog.service('voltha-sim-rest')
+ if not services:
+ print('No voltha-sim-rest service registered in consul; exiting')
+ sys.exit(1)
+ args.sim_rest_endpoint = '{}:{}'.format(services[0]['ServiceAddress'],
+ services[0]['ServicePort'])
+
+ c = VolthaCli(args.grpc_endpoint, args.sim_rest_endpoint,
+ args.global_request)
+ c.poutput(banner)
+ c.load_history()
+ c.cmdloop()
+ c.save_history()
diff --git a/python/cli/omci.py b/python/cli/omci.py
new file mode 100644
index 0000000..d8b8334
--- /dev/null
+++ b/python/cli/omci.py
@@ -0,0 +1,356 @@
+#!/usr/bin/env python
+#
+# Copyright 2018 the original author or authors.
+#
+# 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.
+#
+
+"""
+OpenOMCI level CLI commands
+"""
+from optparse import make_option
+from cmd2 import Cmd, options
+from datetime import datetime
+from google.protobuf.empty_pb2 import Empty
+from table import print_pb_list_as_table
+from python.protos import third_party
+from python.protos import voltha_pb2
+from python.protos.omci_mib_db_pb2 import MibDeviceData, MibClassData, \
+ MibInstanceData
+from os import linesep
+
+_ = third_party
+
+
+class OmciCli(Cmd):
+ CREATED_KEY = 'created'
+ MODIFIED_KEY = 'modified'
+ MDS_KEY = 'mib_data_sync'
+ LAST_SYNC_KEY = 'last_mib_sync'
+ VERSION_KEY = 'version'
+ DEVICE_ID_KEY = 'device_id'
+ CLASS_ID_KEY = 'class_id'
+ INSTANCE_ID_KEY = 'instance_id'
+ ATTRIBUTES_KEY = 'attributes'
+ TIME_FORMAT = '%Y%m%d-%H%M%S.%f'
+ ME_KEY = 'managed_entities'
+ MSG_TYPE_KEY = 'message_types'
+
+ MSG_TYPE_TO_NAME = {
+ 4: 'Create',
+ 5: 'Create Complete',
+ 6: 'Delete',
+ 8: 'Set',
+ 9: 'Get',
+ 10: 'Get Complete',
+ 11: 'Get All Alarms',
+ 12: 'Get All Alarms Next',
+ 13: 'Mib Upload',
+ 14: 'Mib Upload Next',
+ 15: 'Mib Reset',
+ 16: 'Alarm Notification',
+ 17: 'Attribute Value Change',
+ 18: 'Test',
+ 19: 'Start Software Download',
+ 20: 'Download Section',
+ 21: 'End Software Download',
+ 22: 'Activate Software',
+ 23: 'Commit Software',
+ 24: 'Synchronize Time',
+ 25: 'Reboot',
+ 26: 'Get Next',
+ 27: 'Test Result',
+ 28: 'Get Current Data',
+ 29: 'Set Table'
+ }
+
+ def __init__(self, device_id, get_stub):
+ Cmd.__init__(self)
+ self.get_stub = get_stub
+ self.device_id = device_id
+ self.prompt = '(' + self.colorize(
+ self.colorize('omci {}'.format(device_id), 'green'),
+ 'bold') + ') '
+
+ def cmdloop(self, intro=None):
+ self._cmdloop()
+
+ do_exit = Cmd.do_quit
+
+ def do_quit(self, line):
+ return self._STOP_AND_EXIT
+
+ def get_device_mib(self, device_id, depth=-1):
+ stub = self.get_stub()
+
+ try:
+ res = stub.GetMibDeviceData(voltha_pb2.ID(id=device_id),
+ metadata=(('get-depth', str(depth)), ))
+ except Exception as e:
+ pass
+
+ return res
+
+ def help_show_mib(self):
+ self.poutput('show_mib [-d <device-id>] [-c <class-id> [-i <instance-id>]]' +
+ linesep + '-d: <device-id> ONU Device ID' +
+ linesep + '-c: <class-id> Managed Entity Class ID' +
+ linesep + '-i: <instance-id> ME Instance ID')
+
+ @options([
+ make_option('-d', '--device-id', action="store", dest='device_id', type='string',
+ help='ONU Device ID', default=None),
+ make_option('-c', '--class-id', action="store", dest='class_id',
+ type='int', help='Managed Entity Class ID', default=None),
+ make_option('-i', '--instance-id', action="store", dest='instance_id',
+ type='int', help='ME Instance ID', default=None)
+ ])
+ def do_show_mib(self, _line, opts):
+ """
+ Show OMCI MIB Database Information
+ """
+ device_id = opts.device_id or self.device_id
+
+ if opts.class_id is not None and not 1 <= opts.class_id <= 0xFFFF:
+ self.poutput(self.colorize('Error: ', 'red') +
+ self.colorize('Class ID must be 1..65535', 'blue'))
+ return
+
+ if opts.instance_id is not None and opts.class_id is None:
+ self.poutput(self.colorize('Error: ', 'red') +
+ self.colorize('Class ID required if specifying an Instance ID',
+ 'blue'))
+ return
+
+ if opts.instance_id is not None and not 0 <= opts.instance_id <= 0xFFFF:
+ self.poutput(self.colorize('Error: ', 'red') +
+ self.colorize('Instance ID must be 0..65535', 'blue'))
+ return
+
+ try:
+ mib_db = self.get_device_mib(device_id, depth=-1)
+
+ except Exception: # UnboundLocalError if Device ID not found in DB
+ self.poutput(self.colorize('Failed to get MIB database for ONU {}'
+ .format(device_id), 'red'))
+ return
+
+ mib = self._device_to_dict(mib_db)
+
+ self.poutput('OpenOMCI MIB Database for ONU {}'.format(device_id))
+
+ if opts.class_id is None and opts.instance_id is None:
+ self.poutput('Version : {}'.format(mib[OmciCli.VERSION_KEY]))
+ self.poutput('Created : {}'.format(mib[OmciCli.CREATED_KEY]))
+ self.poutput('Last In-Sync Time : {}'.format(mib[OmciCli.LAST_SYNC_KEY]))
+ self.poutput('MIB Data Sync Value: {}'.format(mib[OmciCli.MDS_KEY]))
+
+ class_ids = [k for k in mib.iterkeys()
+ if isinstance(k, int) and
+ (opts.class_id is None or opts.class_id == k)]
+ class_ids.sort()
+
+ if len(class_ids) == 0 and opts.class_id is not None:
+ self.poutput(self.colorize('Class ID {} not found in MIB Database'
+ .format(opts.class_id), 'red'))
+ return
+
+ for cls_id in class_ids:
+ class_data = mib[cls_id]
+ self.poutput(' ----------------------------------------------')
+ self.poutput(' Class ID: {0} - ({0:#x})'.format(cls_id))
+
+ inst_ids = [k for k in class_data.iterkeys()
+ if isinstance(k, int) and
+ (opts.instance_id is None or opts.instance_id == k)]
+ inst_ids.sort()
+
+ if len(inst_ids) == 0 and opts.instance_id is not None:
+ self.poutput(self.colorize('Instance ID {} of Class ID {} not ' +
+ 'found in MIB Database'.
+ format(opts.instance_id, opts.class_id),
+ 'red'))
+ return
+
+ for inst_id in inst_ids:
+ inst_data = class_data[inst_id]
+ self.poutput(' Instance ID: {0} - ({0:#x})'.format(inst_id))
+ self.poutput(' Created : {}'.format(inst_data[OmciCli.CREATED_KEY]))
+ self.poutput(' Modified : {}'.format(inst_data[OmciCli.MODIFIED_KEY]))
+
+ attributes = inst_data[OmciCli.ATTRIBUTES_KEY]
+ attr_names = attributes.keys()
+ attr_names.sort()
+ max_len = max([len(attr) for attr in attr_names])
+
+ for attr in attr_names:
+ name = self._cleanup_attribute_name(attr).ljust(max_len)
+ value = attributes[attr]
+ try:
+ ivalue = int(value)
+ self.poutput(' {0}: {1} - ({1:#x})'.format(name, ivalue))
+
+ except ValueError:
+ self.poutput(' {}: {}'.format(name, value))
+
+ if inst_id is not inst_ids[-1]:
+ self.poutput(linesep)
+
+ def _cleanup_attribute_name(self, attr):
+ """Change underscore to space and capitalize first character"""
+ return ' '.join([v[0].upper() + v[1:] for v in attr.split('_')])
+
+ def _instance_to_dict(self, instance):
+ if not isinstance(instance, MibInstanceData):
+ raise TypeError('{} is not of type MibInstanceData'.format(type(instance)))
+
+ data = {
+ OmciCli.INSTANCE_ID_KEY: instance.instance_id,
+ OmciCli.CREATED_KEY: self._string_to_time(instance.created),
+ OmciCli.MODIFIED_KEY: self._string_to_time(instance.modified),
+ OmciCli.ATTRIBUTES_KEY: dict()
+ }
+ for attribute in instance.attributes:
+ data[OmciCli.ATTRIBUTES_KEY][attribute.name] = str(attribute.value)
+
+ return data
+
+ def _class_to_dict(self, val):
+ if not isinstance(val, MibClassData):
+ raise TypeError('{} is not of type MibClassData'.format(type(val)))
+
+ data = {
+ OmciCli.CLASS_ID_KEY: val.class_id,
+ }
+ for instance in val.instances:
+ data[instance.instance_id] = self._instance_to_dict(instance)
+ return data
+
+ def _device_to_dict(self, val):
+ if not isinstance(val, MibDeviceData):
+ raise TypeError('{} is not of type MibDeviceData'.format(type(val)))
+
+ data = {
+ OmciCli.DEVICE_ID_KEY: val.device_id,
+ OmciCli.CREATED_KEY: self._string_to_time(val.created),
+ OmciCli.LAST_SYNC_KEY: self._string_to_time(val.last_sync_time),
+ OmciCli.MDS_KEY: val.mib_data_sync,
+ OmciCli.VERSION_KEY: val.version,
+ OmciCli.ME_KEY: dict(),
+ OmciCli.MSG_TYPE_KEY: set()
+ }
+ for class_data in val.classes:
+ data[class_data.class_id] = self._class_to_dict(class_data)
+
+ for managed_entity in val.managed_entities:
+ data[OmciCli.ME_KEY][managed_entity.class_id] = managed_entity.name
+
+ for msg_type in val.message_types:
+ data[OmciCli.MSG_TYPE_KEY].add(msg_type.message_type)
+
+ return data
+
+ def _string_to_time(self, time):
+ return datetime.strptime(time, OmciCli.TIME_FORMAT) if len(time) else None
+
+ def help_show_me(self):
+ self.poutput('show_me [-d <device-id>]' +
+ linesep + '-d: <device-id> ONU Device ID')
+
+ @options([
+ make_option('-d', '--device-id', action="store", dest='device_id', type='string',
+ help='ONU Device ID', default=None),
+ ])
+ def do_show_me(self, _line, opts):
+ """ Show supported OMCI Managed Entities"""
+
+ device_id = opts.device_id or self.device_id
+
+ try:
+ mib_db = self.get_device_mib(device_id, depth=1)
+ mib = self._device_to_dict(mib_db)
+
+ except Exception: # UnboundLocalError if Device ID not found in DB
+ self.poutput(self.colorize('Failed to get supported ME information for ONU {}'
+ .format(device_id), 'red'))
+ return
+
+ class_ids = [class_id for class_id in mib[OmciCli.ME_KEY].keys()]
+ class_ids.sort()
+
+ self.poutput('Supported Managed Entities for ONU {}'.format(device_id))
+ for class_id in class_ids:
+ self.poutput(' {0} - ({0:#x}): {1}'.format(class_id,
+ mib[OmciCli.ME_KEY][class_id]))
+
+ def help_show_msg_types(self):
+ self.poutput('show_msg_types [-d <device-id>]' +
+ linesep + '-d: <device-id> ONU Device ID')
+
+ @options([
+ make_option('-d', '--device-id', action="store", dest='device_id', type='string',
+ help='ONU Device ID', default=None),
+ ])
+ def do_show_msg_types(self, _line, opts):
+ """ Show supported OMCI Message Types"""
+ device_id = opts.device_id or self.device_id
+
+ try:
+ mib_db = self.get_device_mib(device_id, depth=1)
+ mib = self._device_to_dict(mib_db)
+
+ except Exception: # UnboundLocalError if Device ID not found in DB
+ self.poutput(self.colorize('Failed to get supported Message Types for ONU {}'
+ .format(device_id), 'red'))
+ return
+
+ msg_types = [msg_type for msg_type in mib[OmciCli.MSG_TYPE_KEY]]
+ msg_types.sort()
+
+ self.poutput('Supported Message Types for ONU {}'.format(device_id))
+ for msg_type in msg_types:
+ self.poutput(' {0} - ({0:#x}): {1}'.
+ format(msg_type,
+ OmciCli.MSG_TYPE_TO_NAME.get(msg_type, 'Unknown')))
+
+ def get_devices(self):
+ stub = self.get_stub()
+ res = stub.ListDevices(Empty())
+ return res.items
+
+ def do_devices(self, line):
+ """List devices registered in Voltha reduced for OMCI menu"""
+ devices = self.get_devices()
+ omit_fields = {
+ 'adapter',
+ 'model',
+ 'hardware_version',
+ 'images',
+ 'firmware_version',
+ 'serial_number',
+ 'vlan',
+ 'root',
+ 'extra_args',
+ 'proxy_address',
+ }
+ print_pb_list_as_table('Devices:', devices, omit_fields, self.poutput)
+
+ def help_devices(self):
+ self.poutput('TODO: Provide some help')
+
+ def poutput(self, msg):
+ """Convenient shortcut for self.stdout.write(); adds newline if necessary."""
+ if msg:
+ self.stdout.write(msg)
+ if msg[-1] != '\n':
+ self.stdout.write('\n')
diff --git a/python/cli/setup.sh b/python/cli/setup.sh
new file mode 100755
index 0000000..6cab0bf
--- /dev/null
+++ b/python/cli/setup.sh
@@ -0,0 +1,43 @@
+#!/bin/bash
+# Copyright 2017-present Open Networking Foundation
+#
+# 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.
+
+while getopts LGC:g:s: option
+do
+ case "${option}"
+ in
+ L) LOOKUP_OPT="-L";;
+ G) GLOBAL_REQUEST_OPT="-G";;
+ C) CONSUL_OPT="-C ${OPTARG}";;
+ g) GRPC_OPT="-g ${OPTARG}";;
+ s) SIM_OPT="-s ${OPTARG}";;
+ esac
+done
+
+if [ -z "$CONSUL_OPT" ]
+then
+ CONSUL_OPT="-C $DOCKER_HOST_IP:8500"
+fi
+
+echo "export DOCKER_HOST_IP=$DOCKER_HOST_IP" > /home/voltha/.bashrc
+echo "export PYTHONPATH=/voltha" >> /home/voltha/.bashrc
+echo "export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" >> /home/voltha/.bashrc
+echo "export DOCKER_HOST_IP=$DOCKER_HOST_IP" > /home/voltha/.bash_profile
+echo "export PYTHONPATH=/voltha" >> /home/voltha/.bash_profile
+echo "export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" >> /home/voltha/.bash_profile
+echo "/voltha/python/cli/main.py $LOOKUP_OPT $GLOBAL_REQUEST_OPT $CONSUL_OPT $GRPC_OPT $SIM_OPT" >> /home/voltha/.bash_profile
+echo "logout" >> /home/voltha/.bash_profile
+chown voltha.voltha /home/voltha/.bash_profile
+/usr/sbin/sshd -D
+
diff --git a/python/cli/table.py b/python/cli/table.py
new file mode 100644
index 0000000..7e6a4d8
--- /dev/null
+++ b/python/cli/table.py
@@ -0,0 +1,204 @@
+#
+# Copyright 2016 the original author or authors.
+#
+# 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.
+#
+import sys
+
+from google.protobuf.message import Message
+from termcolor import colored
+
+_printfn = lambda l: sys.stdout.write(l + '\n')
+
+
+class TablePrinter(object):
+ """Simple tabular data printer utility. For usage, see bottom of file"""
+
+ def __init__(self):
+ self.max_field_lengths = {}
+ self.field_names = {}
+ self.cell_values = {}
+
+ def add_cell(self, row_number, field_key, field_name, value):
+ if not isinstance(value, str):
+ value = str(value)
+ self._add_field_type(field_key, field_name)
+ row = self.cell_values.setdefault(row_number, {})
+ row[field_key] = value
+ self._update_max_length(field_key, value)
+
+ def number_of_rows(self):
+ return len(self.cell_values)
+
+ def print_table(self, header=None, printfn=_printfn, dividers=10):
+
+ if header is not None:
+ printfn(header)
+
+ field_keys = sorted(self.field_names.keys())
+
+ if not field_keys:
+ printfn('table empty')
+ return
+
+ def p_sep():
+ printfn('+' + '+'.join(
+ [(self.max_field_lengths[k] + 2) * '-'
+ for k in field_keys]) + '+')
+
+ p_sep()
+
+ printfn('| ' + ' | '.join(
+ '%%%ds' % self.max_field_lengths[k] % self.field_names[k]
+ for k in field_keys) + ' |')
+ p_sep()
+
+ for i in range(len(self.cell_values)):
+ row = self.cell_values[i]
+ printfn(colored('| ' + ' | '.join(
+ '%%%ds' % self.max_field_lengths[k] % row.get(k, '')
+ for k in field_keys
+ ) + ' |'))
+ if not ((i + 1) % dividers):
+ p_sep()
+
+ if (i + 1) % dividers:
+ p_sep()
+
+ def _update_max_length(self, field_key, string):
+ length = len(string)
+ if length > self.max_field_lengths.get(field_key, 0):
+ self.max_field_lengths[field_key] = length
+
+ def _add_field_type(self, field_key, field_name):
+ if field_key not in self.field_names:
+ self.field_names[field_key] = field_name
+ self._update_max_length(field_key, field_name)
+ else:
+ assert self.field_names[field_key] == field_name
+
+
+def print_pb_list_as_table(header, items, fields_to_omit=None,
+ printfn=_printfn, dividers=10, show_nulls=False,
+ presfns={}):
+ from utils import pb2dict
+
+ t = TablePrinter()
+ for row, obj in enumerate(items):
+ assert isinstance(obj, Message)
+
+ def set_row(pd_dict, _row, field, value, t, prefix,
+ fields_to_omit, number):
+ fname = prefix + field.name
+ if fname in fields_to_omit:
+ return
+ if isinstance(value, Message):
+ add(_row, value, fname + '.',
+ 100 * (number + field.number))
+ else:
+ presentationfn = presfns[fname] if fname in presfns else lambda x: x
+ t.add_cell(_row, number + field.number, fname,
+ presentationfn(pd_dict.get(field.name)))
+
+ def add(_row, pb, prefix='', number=0):
+ d = pb2dict(pb)
+ if show_nulls:
+ fields = pb.DESCRIPTOR.fields
+ for field in fields:
+ set_row(d,
+ _row,
+ field,
+ getattr(pb, field.name),
+ t,
+ prefix,
+ fields_to_omit,
+ number)
+ else:
+ fields = pb.ListFields()
+ for (field, value) in fields:
+ set_row(d,
+ _row,
+ field,
+ value,
+ t,
+ prefix,
+ fields_to_omit,
+ number)
+ add(row, obj)
+
+ t.print_table(header, printfn, dividers)
+
+
+def print_pb_as_table(header, pb, fields_to_omit={}, printfn=_printfn,
+ show_nulls=False):
+
+ from utils import pb2dict
+
+ def is_repeated_item(msg):
+ return hasattr(msg, "extend")
+
+ def set_cell(pb, field, value, t, prefix, fields_to_omit):
+ d = pb2dict(pb)
+ fname = prefix + field.name
+
+ if fname in fields_to_omit:
+ return
+ if isinstance(value, Message):
+ pr(value, fname + '.')
+ elif is_repeated_item(value): # handles any list
+ row = t.number_of_rows()
+ t.add_cell(row, 0, 'field', fname)
+ t.add_cell(row, 1, 'value',
+ '{} item(s)'.format(len(d.get(field.name))))
+ else:
+ row = t.number_of_rows()
+ t.add_cell(row, 0, 'field', fname)
+ t.add_cell(row, 1, 'value', value)
+
+
+ t = TablePrinter()
+
+ def pr(_pb, prefix=''):
+ if show_nulls:
+ fields = _pb.DESCRIPTOR.fields
+ for field in sorted(fields, key=lambda f: f.number):
+ set_cell(_pb,
+ field,
+ getattr(_pb, field.name),
+ t,
+ prefix,
+ fields_to_omit)
+ else:
+ fields = _pb.ListFields()
+ for (field, value) in sorted(fields, key=lambda (f, v): f.number):
+ set_cell(_pb,
+ field,
+ value,
+ t,
+ prefix,
+ fields_to_omit)
+
+ pr(pb)
+
+ t.print_table(header, printfn)
+
+
+if __name__ == '__main__':
+ import random
+
+ t = TablePrinter()
+ for row in range(10):
+ t.add_cell(row, 0, 'id', row + 100)
+ t.add_cell(row, 1, 'name', 'Joe Somebody')
+ t.add_cell(row, 2, 'ows', '${}'.format(random.randint(10, 100000)))
+ t.print_table()
diff --git a/python/cli/utils.py b/python/cli/utils.py
new file mode 100644
index 0000000..38e5ee2
--- /dev/null
+++ b/python/cli/utils.py
@@ -0,0 +1,186 @@
+#
+# Copyright 2016 the original author or authors.
+#
+# 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.
+#
+
+import sys
+
+from google.protobuf.json_format import MessageToDict
+from termcolor import cprint, colored
+
+from table import TablePrinter
+
+
+_printfn = lambda l: sys.stdout.write(l + '\n')
+
+
+def pb2dict(pb_msg):
+ d = MessageToDict(pb_msg, including_default_value_fields=1,
+ preserving_proto_field_name=1)
+ return d
+
+
+def p_cookie(cookie):
+ cookie = '%x' % int(cookie)
+ if len(cookie) > 8:
+ return '~' + cookie[len(cookie)-8:]
+ else:
+ return cookie
+
+'''
+ OFPP_NORMAL = 0x7ffffffa; /* Forward using non-OpenFlow pipeline. */
+ OFPP_FLOOD = 0x7ffffffb; /* Flood using non-OpenFlow pipeline. */
+ OFPP_ALL = 0x7ffffffc; /* All standard ports except input port. */
+ OFPP_CONTROLLER = 0x7ffffffd; /* Send to controller. */
+ OFPP_LOCAL = 0x7ffffffe; /* Local openflow "port". */
+ OFPP_ANY = 0x7fffffff; /* Special value used in some requests when
+'''
+
+
+def p_port(port):
+ if port & 0x7fffffff == 0x7ffffffa:
+ return 'NORMAL'
+ elif port & 0x7fffffff == 0x7ffffffb:
+ return 'FLOOD'
+ elif port & 0x7fffffff == 0x7ffffffc:
+ return 'ALL'
+ elif port & 0x7fffffff == 0x7ffffffd:
+ return 'CONTROLLER'
+ elif port & 0x7fffffff == 0x7ffffffe:
+ return 'LOCAL'
+ elif port & 0x7fffffff == 0x7fffffff:
+ return 'ANY'
+ else:
+ return str(port)
+
+
+def p_vlan_vid(vlan_vid):
+ if vlan_vid == 0:
+ return 'untagged'
+ assert vlan_vid & 4096 == 4096
+ return str(vlan_vid - 4096)
+
+
+def p_ipv4(x):
+ return '.'.join(str(v) for v in [
+ (x >> 24) & 0xff, (x >> 16) & 0xff, (x >> 8) & 0xff, x & 0xff
+ ])
+
+
+field_printers = {
+ 'IN_PORT': lambda f: (100, 'in_port', p_port(f['port'])),
+ 'VLAN_VID': lambda f: (101, 'vlan_vid', p_vlan_vid(f['vlan_vid'])),
+ 'VLAN_PCP': lambda f: (102, 'vlan_pcp', str(f['vlan_pcp'])),
+ 'ETH_TYPE': lambda f: (103, 'eth_type', '%X' % f['eth_type']),
+ 'IP_PROTO': lambda f: (104, 'ip_proto', str(f['ip_proto'])),
+ 'IPV4_DST': lambda f: (105, 'ipv4_dst', p_ipv4(f['ipv4_dst'])),
+ 'UDP_SRC': lambda f: (106, 'udp_src', str(f['udp_src'])),
+ 'UDP_DST': lambda f: (107, 'udp_dst', str(f['udp_dst'])),
+ 'TCP_SRC': lambda f: (108, 'tcp_src', str(f['tcp_src'])),
+ 'TCP_DST': lambda f: (109, 'tcp_dst', str(f['tcp_dst'])),
+ 'METADATA': lambda f: (110, 'metadata', str(f['table_metadata'])),
+}
+
+
+def p_field(field):
+ assert field['oxm_class'].endswith('OPENFLOW_BASIC')
+ ofb = field['ofb_field']
+ assert not ofb['has_mask']
+ type = ofb['type'][len('OFPXMT_OFB_'):]
+ weight, field_name, value = field_printers[type](ofb)
+ return 1000 + weight, 'set_' + field_name, value
+
+
+action_printers = {
+ 'SET_FIELD': lambda a: p_field(a['set_field']['field']),
+ 'POP_VLAN': lambda a: (2000, 'pop_vlan', 'Yes'),
+ 'PUSH_VLAN': lambda a: (2001, 'push_vlan', '%x' % a['push']['ethertype']),
+ 'GROUP': lambda a: (3000, 'group', p_port(a['group']['group_id'])),
+ 'OUTPUT': lambda a: (4000, 'output', p_port(a['output']['port'])),
+}
+
+
+def print_flows(what, id, type, flows, groups, printfn=_printfn):
+
+ header = ''.join([
+ '{} '.format(what),
+ colored(id, color='green', attrs=['bold']),
+ ' (type: ',
+ colored(type, color='blue'),
+ ')'
+ ]) + '\nFlows ({}):'.format(len(flows))
+
+ table = TablePrinter()
+ for i, flow in enumerate(flows):
+
+ table.add_cell(i, 0, 'table_id', value=str(flow['table_id']))
+ table.add_cell(i, 1, 'priority', value=str(flow['priority']))
+ table.add_cell(i, 2, 'cookie', p_cookie(flow['cookie']))
+
+ assert flow['match']['type'] == 'OFPMT_OXM'
+ for field in flow['match']['oxm_fields']:
+ assert field['oxm_class'].endswith('OPENFLOW_BASIC')
+ ofb = field['ofb_field']
+ # see CORD-816 (https://jira.opencord.org/browse/CORD-816)
+ assert not ofb['has_mask'], 'masked match not handled yet'
+ type = ofb['type'][len('OFPXMT_OFB_'):]
+ table.add_cell(i, *field_printers[type](ofb))
+
+ for instruction in flow['instructions']:
+ itype = instruction['type']
+ if itype == 4:
+ for action in instruction['actions']['actions']:
+ atype = action['type'][len('OFPAT_'):]
+ table.add_cell(i, *action_printers[atype](action))
+ elif itype == 1:
+ table.add_cell(i, 10000, 'goto-table',
+ instruction['goto_table']['table_id'])
+ elif itype == 5:
+ table.add_cell(i, 10000, 'clear-actions', [])
+ else:
+ raise NotImplementedError(
+ 'not handling instruction type {}'.format(itype))
+
+ table.print_table(header, printfn)
+
+
+def print_groups(what, id, type, groups, printfn=_printfn):
+ header = ''.join([
+ '{} '.format(what),
+ colored(id, color='green', attrs=['bold']),
+ ' (type: ',
+ colored(type, color='blue'),
+ ')'
+ ]) + '\nGroups ({}):'.format(len(groups))
+
+ table = TablePrinter()
+ for i, group in enumerate(groups):
+ output_ports = []
+ for bucket in group['desc']['buckets']:
+ for action in bucket['actions']:
+ if action['type'] == 'OFPAT_OUTPUT':
+ output_ports.append(action['output']['port'])
+ table.add_cell(i, 0, 'group_id', value=str(group['desc']['group_id']))
+ table.add_cell(i, 1, 'buckets', value=str(dict(output=output_ports)))
+
+ table.print_table(header, printfn)
+
+def dict2line(d):
+ assert isinstance(d, dict)
+ return ', '.join('{}: {}'.format(k, v) for k, v in sorted(d.items()))
+
+def enum2name(msg_obj, enum_type, enum_value):
+ descriptor = msg_obj.DESCRIPTOR.enum_types_by_name[enum_type]
+ name = descriptor.values_by_number[enum_value].name
+ return name
diff --git a/python/common/frameio/frameio.py b/python/common/frameio/frameio.py
deleted file mode 100644
index 3f5bcf6..0000000
--- a/python/common/frameio/frameio.py
+++ /dev/null
@@ -1,437 +0,0 @@
-#
-# Copyright 2017 the original author or authors.
-#
-# 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.
-#
-
-"""
-A module that can send and receive raw ethernet frames on a set of interfaces
-and it can manage a set of vlan interfaces on top of existing
-interfaces. Due to reliance on raw sockets, this module requires
-root access. Also, raw sockets are hard to deal with in Twisted (not
-directly supported) we need to run the receiver select loop on a dedicated
-thread.
-"""
-
-import os
-import socket
-import struct
-import uuid
-from pcapy import BPFProgram
-from threading import Thread, Condition
-
-import fcntl
-
-import select
-import structlog
-import sys
-
-from scapy.data import ETH_P_ALL
-from twisted.internet import reactor
-from zope.interface import implementer
-
-from voltha.registry import IComponent
-
-if sys.platform.startswith('linux'):
- from common.frameio.third_party.oftest import afpacket, netutils
-elif sys.platform == 'darwin':
- from scapy.arch import pcapdnet, BIOCIMMEDIATE, dnet
-
-log = structlog.get_logger()
-
-
-def hexify(buffer):
- """
- Return a hexadecimal string encoding of input buffer
- """
- return ''.join('%02x' % ord(c) for c in buffer)
-
-
-class _SelectWakerDescriptor(object):
- """
- A descriptor that can be mixed into a select loop to wake it up.
- """
- def __init__(self):
- self.pipe_read, self.pipe_write = os.pipe()
- fcntl.fcntl(self.pipe_write, fcntl.F_SETFL, os.O_NONBLOCK)
-
- def __del__(self):
- os.close(self.pipe_read)
- os.close(self.pipe_write)
-
- def fileno(self):
- return self.pipe_read
-
- def wait(self):
- os.read(self.pipe_read, 1)
-
- def notify(self):
- """Trigger a select loop"""
- os.write(self.pipe_write, '\x00')
-
-
-class BpfProgramFilter(object):
- """
- Convenience packet filter based on the well-tried Berkeley Packet Filter,
- used by many well known open source tools such as pcap and tcpdump.
- """
- def __init__(self, program_string):
- """
- Create a filter using the BPF command syntax. To learn more,
- consult 'man pcap-filter'.
- :param program_string: The textual definition of the filter. Examples:
- 'vlan 1000'
- 'vlan 1000 and ip src host 10.10.10.10'
- """
- self.bpf = BPFProgram(program_string)
-
- def __call__(self, frame):
- """
- Return 1 if frame passes filter.
- :param frame: Raw frame provided as Python string
- :return: 1 if frame satisfies filter, 0 otherwise.
- """
- return self.bpf.filter(frame)
-
-
-class FrameIOPort(object):
- """
- Represents a network interface which we can send/receive raw
- Ethernet frames.
- """
-
- RCV_SIZE_DEFAULT = 4096
- ETH_P_ALL = 0x03
- RCV_TIMEOUT = 10000
- MIN_PKT_SIZE = 60
-
- def __init__(self, iface_name):
- self.iface_name = iface_name
- self.proxies = []
- self.socket = self.open_socket(self.iface_name)
- log.debug('socket-opened', fn=self.fileno(), iface=iface_name)
- self.received = 0
- self.discarded = 0
-
- def add_proxy(self, proxy):
- self.proxies.append(proxy)
-
- def del_proxy(self, proxy):
- self.proxies = [p for p in self.proxies if p.name != proxy.name]
-
- def open_socket(self, iface_name):
- raise NotImplementedError('to be implemented by derived class')
-
- def rcv_frame(self):
- raise NotImplementedError('to be implemented by derived class')
-
- def __del__(self):
- if self.socket:
- self.socket.close()
- self.socket = None
- log.debug('socket-closed', iface=self.iface_name)
-
- def fileno(self):
- return self.socket.fileno()
-
- def _dispatch(self, proxy, frame):
- log.debug('calling-publisher', proxy=proxy.name, frame=hexify(frame))
- try:
- proxy.callback(proxy, frame)
- except Exception as e:
- log.exception('callback-error',
- explanation='Callback failed while processing frame',
- e=e)
-
- def recv(self):
- """Called on the select thread when a packet arrives"""
- try:
- frame = self.rcv_frame()
- except RuntimeError as e:
- # we observed this happens sometimes right after the socket was
- # attached to a newly created veth interface. So we log it, but
- # allow to continue.
- log.warn('afpacket-recv-error', code=-1)
- return
-
- log.debug('frame-received', iface=self.iface_name, len=len(frame),
- hex=hexify(frame))
- self.received +=1
- dispatched = False
- for proxy in self.proxies:
- if proxy.filter is None or proxy.filter(frame):
- log.debug('frame-dispatched')
- dispatched = True
- reactor.callFromThread(self._dispatch, proxy, frame)
-
- if not dispatched:
- self.discarded += 1
- log.debug('frame-discarded')
-
- def send(self, frame):
- log.debug('sending', len=len(frame), iface=self.iface_name)
- sent_bytes = self.send_frame(frame)
- if sent_bytes != len(frame):
- log.error('send-error', iface=self.iface_name,
- wanted_to_send=len(frame), actually_sent=sent_bytes)
- return sent_bytes
-
- def send_frame(self, frame):
- try:
- return self.socket.send(frame)
- except socket.error, err:
- if err[0] == os.errno.EINVAL:
- if len(frame) < self.MIN_PKT_SIZE:
- padding = '\x00' * (self.MIN_PKT_SIZE - len(frame))
- frame = frame + padding
- return self.socket.send(frame)
- else:
- raise
-
- def up(self):
- if sys.platform.startswith('darwin'):
- pass
- else:
- os.system('ip link set {} up'.format(self.iface_name))
- return self
-
- def down(self):
- if sys.platform.startswith('darwin'):
- pass
- else:
- os.system('ip link set {} down'.format(self.iface_name))
- return self
-
- def statistics(self):
- return self.received, self.discarded
-
-
-class LinuxFrameIOPort(FrameIOPort):
-
- def open_socket(self, iface_name):
- s = socket.socket(socket.AF_PACKET, socket.SOCK_RAW, 0)
- afpacket.enable_auxdata(s)
- s.bind((self.iface_name, self.ETH_P_ALL))
- netutils.set_promisc(s, iface_name)
- s.settimeout(self.RCV_TIMEOUT)
- return s
-
- def rcv_frame(self):
- return afpacket.recv(self.socket, self.RCV_SIZE_DEFAULT)
-
-
-class DarwinFrameIOPort(FrameIOPort):
-
- def open_socket(self, iface_name):
- sin = pcapdnet.open_pcap(iface_name, 1600, 1, 100)
- try:
- fcntl.ioctl(sin.fileno(), BIOCIMMEDIATE, struct.pack("I",1))
- except:
- pass
-
- # need a different kind of socket for sending out
- self.sout = dnet.eth(iface_name)
-
- return sin
-
- def send_frame(self, frame):
- return self.sout.send(frame)
-
- def rcv_frame(self):
- pkt = self.socket.next()
- if pkt is not None:
- ts, pkt = pkt
- return pkt
-
-
-if sys.platform == 'darwin':
- _FrameIOPort = DarwinFrameIOPort
-elif sys.platform.startswith('linux'):
- _FrameIOPort = LinuxFrameIOPort
-else:
- raise Exception('Unsupported platform {}'.format(sys.platform))
- sys.exit(1)
-
-
-class FrameIOPortProxy(object):
- """Makes FrameIOPort sharable between multiple users"""
-
- def __init__(self, frame_io_port, callback, filter=None, name=None):
- self.frame_io_port = frame_io_port
- self.callback = callback
- self.filter = filter
- self.name = uuid.uuid4().hex[:12] if name is None else name
-
- @property
- def iface_name(self):
- return self.frame_io_port.iface_name
-
- def get_iface_name(self):
- return self.frame_io_port.iface_name
-
- def send(self, frame):
- return self.frame_io_port.send(frame)
-
- def up(self):
- self.frame_io_port.up()
- return self
-
- def down(self):
- self.frame_io_port.down()
- return self
-
-
-@implementer(IComponent)
-class FrameIOManager(Thread):
- """
- Packet/Frame IO manager that can be used to send/receive raw frames
- on a set of network interfaces.
- """
- def __init__(self):
- super(FrameIOManager, self).__init__()
-
- self.ports = {} # iface_name -> ActiveFrameReceiver
- self.queue = {} # iface_name -> TODO
-
- self.cvar = Condition()
- self.waker = _SelectWakerDescriptor()
- self.stopped = False
- self.ports_changed = False
-
- # ~~~~~~~~~~~ exposed methods callable from main thread ~~~~~~~~~~~~~~~~~~~
-
- def start(self):
- """
- Start the IO manager and its select loop thread
- """
- log.debug('starting')
- super(FrameIOManager, self).start()
- log.info('started')
- return self
-
- def stop(self):
- """
- Stop the IO manager and its thread with the select loop
- """
- log.debug('stopping')
- self.stopped = True
- self.waker.notify()
- self.join()
- del self.ports
- log.info('stopped')
-
- def list_interfaces(self):
- """
- Return list of interfaces listened on
- :return: List of FrameIOPort objects
- """
- return self.ports
-
- def open_port(self, iface_name, callback, filter=None, name=None):
- """
- Add a new interface and start receiving on it.
- :param iface_name: Name of the interface. Must be an existing Unix
- interface (eth0, en0, etc.)
- :param callback: Called on each received frame;
- signature: def callback(port, frame) where port is the FrameIOPort
- instance at which the frame was received, frame is the actual frame
- received (as binay string)
- :param filter: An optional filter (predicate), with signature:
- def filter(frame). If provided, only frames for which filter evaluates
- to True will be forwarded to callback.
- :return: FrmaeIOPortProxy instance.
- """
-
- port = self.ports.get(iface_name)
- if port is None:
- port = _FrameIOPort(iface_name)
- self.ports[iface_name] = port
- self.ports_changed = True
- self.waker.notify()
-
- proxy = FrameIOPortProxy(port, callback, filter, name)
- port.add_proxy(proxy)
-
- return proxy
-
- def close_port(self, proxy):
- """
- Remove the proxy. If this is the last proxy on an interface, stop and
- remove the named interface as well
- :param proxy: FrameIOPortProxy reference
- :return: None
- """
- assert isinstance(proxy, FrameIOPortProxy)
- iface_name = proxy.get_iface_name()
- assert iface_name in self.ports, "iface_name {} unknown".format(iface_name)
- port = self.ports[iface_name]
- port.del_proxy(proxy)
-
- if not port.proxies:
- del self.ports[iface_name]
- # need to exit select loop to reconstruct select fd lists
- self.ports_changed = True
- self.waker.notify()
-
- def send(self, iface_name, frame):
- """
- Send frame on given interface
- :param iface_name: Name of previously registered interface
- :param frame: frame as string
- :return: number of bytes sent
- """
- return self.ports[iface_name].send(frame)
-
- # ~~~~~~~~~~~~~ Thread methods (running on non-main thread ~~~~~~~~~~~~~~~~
-
- def run(self):
- """
- Called on the alien thread, this is the core multi-port receive loop
- """
-
- log.debug('select-loop-started')
-
- # outer loop constructs sockets list for select
- while not self.stopped:
- sockets = [self.waker] + self.ports.values()
- self.ports_changed = False
- empty = []
- # inner select loop
-
- while not self.stopped:
- try:
- _in, _out, _err = select.select(sockets, empty, empty, 1)
- except Exception as e:
- log.exception('frame-io-select-error', e=e)
- break
- with self.cvar:
- for port in _in:
- if port is self.waker:
- self.waker.wait()
- continue
- else:
- port.recv()
- self.cvar.notify_all()
- if self.ports_changed:
- break # break inner loop so we reconstruct sockets list
-
- log.debug('select-loop-exited')
-
- def del_interface(self, iface_name):
- """
- Delete interface for stopping
- """
-
- log.info('Delete interface')
- del self.ports[iface_name]
- log.info('Interface(port) is deleted')
diff --git a/python/common/frameio/third_party/__init__.py b/python/common/frameio/third_party/__init__.py
deleted file mode 100644
index b0fb0b2..0000000
--- a/python/common/frameio/third_party/__init__.py
+++ /dev/null
@@ -1,13 +0,0 @@
-# Copyright 2017-present Open Networking Foundation
-#
-# 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.
diff --git a/python/common/frameio/third_party/oftest/LICENSE b/python/common/frameio/third_party/oftest/LICENSE
deleted file mode 100644
index 3216042..0000000
--- a/python/common/frameio/third_party/oftest/LICENSE
+++ /dev/null
@@ -1,36 +0,0 @@
-OpenFlow Test Framework
-
-Copyright (c) 2010 The Board of Trustees of The Leland Stanford
-Junior University
-
-Except where otherwise noted, this software is distributed under
-the OpenFlow Software License. See
-http://www.openflowswitch.org/wp/legal/ for current details.
-
-We are making the OpenFlow specification and associated documentation
-(Software) available for public use and benefit with the expectation
-that others will use, modify and enhance the Software and contribute
-those enhancements back to the community. However, since we would like
-to make the Software available for broadest use, with as few
-restrictions as possible permission is hereby granted, free of charge,
-to any person obtaining a copy of this Software to deal in the
-Software under the copyrights without restriction, including without
-limitation the rights to use, copy, modify, merge, publish,
-distribute, sublicense, and/or sell copies of the Software, and to
-permit persons to whom the Software is furnished to do so, subject to
-the following conditions:
-
-The above copyright notice and this permission notice shall be
-included in all copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED -Y´AS IS¡, WITHOUT WARRANTY OF ANY KIND,
-EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
-LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
-OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
-WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-
-The name and trademarks of copyright holder(s) may NOT be used in
-advertising or publicity pertaining to the Software or any derivatives
-without specific, written prior permission.
diff --git a/python/common/frameio/third_party/oftest/README.md b/python/common/frameio/third_party/oftest/README.md
deleted file mode 100644
index f0cb649..0000000
--- a/python/common/frameio/third_party/oftest/README.md
+++ /dev/null
@@ -1,6 +0,0 @@
-Files in this directory are derived from the respective files
-in oftest (http://github.com/floodlight/oftest).
-
-For the licensing terms of these files, see LICENSE in this dir.
-
-
diff --git a/python/common/frameio/third_party/oftest/__init__.py b/python/common/frameio/third_party/oftest/__init__.py
deleted file mode 100644
index b0fb0b2..0000000
--- a/python/common/frameio/third_party/oftest/__init__.py
+++ /dev/null
@@ -1,13 +0,0 @@
-# Copyright 2017-present Open Networking Foundation
-#
-# 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.
diff --git a/python/common/frameio/third_party/oftest/afpacket.py b/python/common/frameio/third_party/oftest/afpacket.py
deleted file mode 100644
index 9ae8075..0000000
--- a/python/common/frameio/third_party/oftest/afpacket.py
+++ /dev/null
@@ -1,124 +0,0 @@
-# Copyright 2017-present Open Networking Foundation
-#
-# 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.
-"""
-AF_PACKET receive support
-
-When VLAN offload is enabled on the NIC Linux will not deliver the VLAN tag
-in the data returned by recv. Instead, it delivers the VLAN TCI in a control
-message. Python 2.x doesn't have built-in support for recvmsg, so we have to
-use ctypes to call it. The recv function exported by this module reconstructs
-the VLAN tag if it was offloaded.
-"""
-
-import struct
-from ctypes import *
-
-ETH_P_8021Q = 0x8100
-SOL_PACKET = 263
-PACKET_AUXDATA = 8
-TP_STATUS_VLAN_VALID = 1 << 4
-
-class struct_iovec(Structure):
- _fields_ = [
- ("iov_base", c_void_p),
- ("iov_len", c_size_t),
- ]
-
-class struct_msghdr(Structure):
- _fields_ = [
- ("msg_name", c_void_p),
- ("msg_namelen", c_uint32),
- ("msg_iov", POINTER(struct_iovec)),
- ("msg_iovlen", c_size_t),
- ("msg_control", c_void_p),
- ("msg_controllen", c_size_t),
- ("msg_flags", c_int),
- ]
-
-class struct_cmsghdr(Structure):
- _fields_ = [
- ("cmsg_len", c_size_t),
- ("cmsg_level", c_int),
- ("cmsg_type", c_int),
- ]
-
-class struct_tpacket_auxdata(Structure):
- _fields_ = [
- ("tp_status", c_uint),
- ("tp_len", c_uint),
- ("tp_snaplen", c_uint),
- ("tp_mac", c_ushort),
- ("tp_net", c_ushort),
- ("tp_vlan_tci", c_ushort),
- ("tp_padding", c_ushort),
- ]
-
-libc = CDLL("libc.so.6")
-recvmsg = libc.recvmsg
-recvmsg.argtypes = [c_int, POINTER(struct_msghdr), c_int]
-recvmsg.retype = c_int
-
-def enable_auxdata(sk):
- """
- Ask the kernel to return the VLAN tag in a control message
-
- Must be called on the socket before afpacket.recv.
- """
- sk.setsockopt(SOL_PACKET, PACKET_AUXDATA, 1)
-
-def recv(sk, bufsize):
- """
- Receive a packet from an AF_PACKET socket
- @sk Socket
- @bufsize Maximum packet size
- """
- buf = create_string_buffer(bufsize)
-
- ctrl_bufsize = sizeof(struct_cmsghdr) + sizeof(struct_tpacket_auxdata) + sizeof(c_size_t)
- ctrl_buf = create_string_buffer(ctrl_bufsize)
-
- iov = struct_iovec()
- iov.iov_base = cast(buf, c_void_p)
- iov.iov_len = bufsize
-
- msghdr = struct_msghdr()
- msghdr.msg_name = None
- msghdr.msg_namelen = 0
- msghdr.msg_iov = pointer(iov)
- msghdr.msg_iovlen = 1
- msghdr.msg_control = cast(ctrl_buf, c_void_p)
- msghdr.msg_controllen = ctrl_bufsize
- msghdr.msg_flags = 0
-
- rv = recvmsg(sk.fileno(), byref(msghdr), 0)
- if rv < 0:
- raise RuntimeError("recvmsg failed: rv=%d", rv)
-
- # The kernel only delivers control messages we ask for. We
- # only enabled PACKET_AUXDATA, so we can assume it's the
- # only control message.
- assert msghdr.msg_controllen >= sizeof(struct_cmsghdr)
-
- cmsghdr = struct_cmsghdr.from_buffer(ctrl_buf) # pylint: disable=E1101
- assert cmsghdr.cmsg_level == SOL_PACKET
- assert cmsghdr.cmsg_type == PACKET_AUXDATA
-
- auxdata = struct_tpacket_auxdata.from_buffer(ctrl_buf, sizeof(struct_cmsghdr)) # pylint: disable=E1101
-
- if auxdata.tp_vlan_tci != 0 or auxdata.tp_status & TP_STATUS_VLAN_VALID:
- # Insert VLAN tag
- tag = struct.pack("!HH", ETH_P_8021Q, auxdata.tp_vlan_tci)
- return buf.raw[:12] + tag + buf.raw[12:rv]
- else:
- return buf.raw[:rv]
diff --git a/python/common/frameio/third_party/oftest/netutils.py b/python/common/frameio/third_party/oftest/netutils.py
deleted file mode 100644
index 092d490..0000000
--- a/python/common/frameio/third_party/oftest/netutils.py
+++ /dev/null
@@ -1,73 +0,0 @@
-# Copyright 2017-present Open Networking Foundation
-#
-# 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.
-"""
-Network utilities for the OpenFlow test framework
-"""
-
-###########################################################################
-## ##
-## Promiscuous mode enable/disable ##
-## ##
-## Based on code from Scapy by Phillippe Biondi ##
-## ##
-## ##
-## This program is free software; you can redistribute it and/or modify it ##
-## under the terms of the GNU General Public License as published by the ##
-## Free Software Foundation; either version 2, or (at your option) any ##
-## later version. ##
-## ##
-## This program is distributed in the hope that it will be useful, but ##
-## WITHOUT ANY WARRANTY; without even the implied warranty of ##
-## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ##
-## General Public License for more details. ##
-## ##
-#############################################################################
-
-import socket
-from fcntl import ioctl
-import struct
-
-# From net/if_arp.h
-ARPHDR_ETHER = 1
-ARPHDR_LOOPBACK = 772
-
-# From bits/ioctls.h
-SIOCGIFHWADDR = 0x8927 # Get hardware address
-SIOCGIFINDEX = 0x8933 # name -> if_index mapping
-
-# From netpacket/packet.h
-PACKET_ADD_MEMBERSHIP = 1
-PACKET_DROP_MEMBERSHIP = 2
-PACKET_MR_PROMISC = 1
-
-# From bits/socket.h
-SOL_PACKET = 263
-
-def get_if(iff,cmd):
- s=socket.socket()
- ifreq = ioctl(s, cmd, struct.pack("16s16x",iff))
- s.close()
- return ifreq
-
-def get_if_index(iff):
- return int(struct.unpack("I",get_if(iff, SIOCGIFINDEX)[16:20])[0])
-
-def set_promisc(s,iff,val=1):
- mreq = struct.pack("IHH8s", get_if_index(iff), PACKET_MR_PROMISC, 0, "")
- if val:
- cmd = PACKET_ADD_MEMBERSHIP
- else:
- cmd = PACKET_DROP_MEMBERSHIP
- s.setsockopt(SOL_PACKET, cmd, mreq)
-
diff --git a/python/adapters/common/openflow/__init__.py b/python/common/openflow/__init__.py
similarity index 100%
rename from python/adapters/common/openflow/__init__.py
rename to python/common/openflow/__init__.py
diff --git a/python/adapters/common/openflow/utils.py b/python/common/openflow/utils.py
similarity index 99%
rename from python/adapters/common/openflow/utils.py
rename to python/common/openflow/utils.py
index 730c714..456ae06 100644
--- a/python/adapters/common/openflow/utils.py
+++ b/python/common/openflow/utils.py
@@ -15,7 +15,7 @@
#
import structlog
-from adapters.protos import openflow_13_pb2 as ofp
+from python.protos import openflow_13_pb2 as ofp
from hashlib import md5
log = structlog.get_logger()
diff --git a/python/common/utils/consulhelpers.py b/python/common/utils/consulhelpers.py
index df4dd58..853143b 100644
--- a/python/common/utils/consulhelpers.py
+++ b/python/common/utils/consulhelpers.py
@@ -21,7 +21,7 @@
from structlog import get_logger
from consul import Consul
from random import randint
-from common.utils.nethelpers import get_my_primary_local_ipv4
+from nethelpers import get_my_primary_local_ipv4
log = get_logger()
diff --git a/python/adapters/common/utils/registry.py b/python/common/utils/registry.py
similarity index 100%
rename from python/adapters/common/utils/registry.py
rename to python/common/utils/registry.py
diff --git a/python/docker/Dockerfile.adapter_ponsim_olt b/python/docker/Dockerfile.adapter_ponsim_olt
new file mode 100644
index 0000000..0c869de
--- /dev/null
+++ b/python/docker/Dockerfile.adapter_ponsim_olt
@@ -0,0 +1,42 @@
+# Copyright 2016 the original author or authors.
+#
+# 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.
+ARG TAG=latest
+ARG REGISTRY=
+ARG REPOSITORY=
+
+FROM ${REGISTRY}${REPOSITORY}voltha-protos:${TAG} as protos
+FROM ${REGISTRY}${REPOSITORY}voltha-base:${TAG}
+
+MAINTAINER Voltha Community <info@opennetworking.org>
+
+# Bundle app source
+RUN mkdir /voltha && touch /voltha/__init__.py
+ENV PYTHONPATH=/voltha
+COPY common /voltha/python/common/
+COPY adapters/common /voltha/python/adapters/common/
+COPY adapters/kafka /voltha/python/adapters/kafka
+COPY adapters/*.py /voltha/python/adapters/
+COPY adapters/ponsim_olt /voltha/python/adapters/ponsim_olt
+RUN touch /voltha/python/__init__.py
+RUN touch /voltha/python/adapters/__init__.py
+
+# Copy in the generated GRPC proto code
+COPY --from=protos /protos/voltha /voltha/python/protos
+COPY --from=protos /protos/google/api /voltha/python/protos/third_party/google/api
+COPY protos/third_party/__init__.py /voltha/python/protos/third_party
+RUN touch /voltha/python/protos/__init__.py
+RUN touch /voltha/python/protos/third_party/google/__init__.py
+
+# Exposing process and default entry point
+# CMD ["python", "/voltha/python/adapters/ponsim_olt/main.py"]
diff --git a/python/docker/Dockerfile.adapter_ponsim_onu b/python/docker/Dockerfile.adapter_ponsim_onu
new file mode 100644
index 0000000..c8d19f8
--- /dev/null
+++ b/python/docker/Dockerfile.adapter_ponsim_onu
@@ -0,0 +1,42 @@
+# Copyright 2016 the original author or authors.
+#
+# 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.
+ARG TAG=latest
+ARG REGISTRY=
+ARG REPOSITORY=
+
+FROM ${REGISTRY}${REPOSITORY}voltha-protos:${TAG} as protos
+FROM ${REGISTRY}${REPOSITORY}voltha-base:${TAG}
+
+MAINTAINER Voltha Community <info@opennetworking.org>
+
+# Bundle app source
+RUN mkdir /voltha && touch /voltha/__init__.py
+ENV PYTHONPATH=/voltha
+COPY common /voltha/python/common/
+COPY adapters/common /voltha/python/adapters/common/
+COPY adapters/kafka /voltha/python/adapters/kafka
+COPY adapters/*.py /voltha/python/adapters/
+COPY adapters/ponsim_onu /voltha/python/adapters/ponsim_onu
+RUN touch /voltha/python/__init__.py
+RUN touch /voltha/python/adapters/__init__.py
+
+# Copy in the generated GRPC proto code
+COPY --from=protos /protos/voltha /voltha/python/protos
+COPY --from=protos /protos/google/api /voltha/python/protos/third_party/google/api
+COPY protos/third_party/__init__.py /voltha/python/protos/third_party
+RUN touch /voltha/python/protos/__init__.py
+RUN touch /voltha/python/protos/third_party/google/__init__.py
+
+# Exposing process and default entry point
+# CMD ["python", "/voltha/python/adapters/ponsim_onu/main.py"]
diff --git a/python/docker/Dockerfile.cli b/python/docker/Dockerfile.cli
new file mode 100644
index 0000000..2c4fd45
--- /dev/null
+++ b/python/docker/Dockerfile.cli
@@ -0,0 +1,60 @@
+# Copyright 2016 the original author or authors.
+#
+# 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.
+ARG TAG=latest
+ARG REGISTRY=
+ARG REPOSITORY=
+
+FROM ${REGISTRY}${REPOSITORY}voltha-protos:${TAG} as protos
+FROM ${REGISTRY}${REPOSITORY}voltha-base:${TAG}
+
+MAINTAINER Voltha Community <info@opennetworking.org>
+
+# Bundle app source
+RUN mkdir /voltha && touch /voltha/__init__.py
+ENV PYTHONPATH=/voltha
+COPY common /voltha/python/common/
+COPY cli /voltha/python/cli
+RUN touch /voltha/python/__init__.py
+RUN touch /voltha/python/cli/__init__.py
+
+# Copy in the generated GRPC proto code
+COPY --from=protos /protos/voltha /voltha/python/protos
+COPY --from=protos /protos/google/api /voltha/python/protos/third_party/google/api
+COPY protos/third_party/__init__.py /voltha/python/protos/third_party
+RUN touch /voltha/python/protos/__init__.py
+RUN touch /voltha/python/protos/third_party/google/__init__.py
+
+# Setup the voltha user
+RUN useradd -b /home -d /home/voltha voltha -s /bin/bash
+RUN mkdir /home/voltha
+RUN chown voltha.voltha /home/voltha
+RUN echo "voltha:admin" | chpasswd
+RUN apt-get update && apt-get install -y openssh-server
+RUN apt-get update && apt-get install -y openssh-server
+RUN mkdir /var/run/sshd
+RUN echo 'root:screencast' | chpasswd
+RUN sed -i 's/PermitRootLogin prohibit-password/PermitRootLogin yes/' /etc/ssh/sshd_config
+
+# SSH login fix. Otherwise user is kicked off after login
+RUN sed 's@session\s*required\s*pam_loginuid.so@session optional pam_loginuid.so@g' -i /etc/pam.d/sshd
+
+ENV NOTVISIBLE "in users profile"
+RUN echo "export VISIBLE=now" >> /etc/profile
+
+EXPOSE 22
+
+# Exposing process and default entry point
+ENTRYPOINT ["/usr/bin/dumb-init", "--"]
+
+CMD ["/voltha/python/cli/setup.sh"]
diff --git a/python/adapters/docker/Dockerfile.protoc b/python/docker/Dockerfile.protoc
similarity index 100%
rename from python/adapters/docker/Dockerfile.protoc
rename to python/docker/Dockerfile.protoc
diff --git a/python/adapters/docker/Dockerfile.protos b/python/docker/Dockerfile.protos
similarity index 100%
rename from python/adapters/docker/Dockerfile.protos
rename to python/docker/Dockerfile.protos
diff --git a/python/adapters/docker/config/Makefile.protos b/python/docker/config/Makefile.protos
similarity index 100%
rename from python/adapters/docker/config/Makefile.protos
rename to python/docker/config/Makefile.protos
diff --git a/python/adapters/env.sh b/python/env.sh
similarity index 93%
rename from python/adapters/env.sh
rename to python/env.sh
index f4f9f97..ec3b52f 100644
--- a/python/adapters/env.sh
+++ b/python/env.sh
@@ -26,4 +26,4 @@
. $VENVDIR/bin/activate
# add top-level voltha dir to pythonpath
-export PYTHONPATH=$VOLTHA_BASE/$VENVDIR/lib/python2.7/site-packages:$PYTHONPATH:$VOLTHA_BASE:$VOLTHA_BASE/protos/third_party
+export PYTHONPATH=$VOLTHA_BASE/$VENVDIR/lib/python2.7/site-packages:$PYTHONPATH:$VOLTHA_BASE:$VOLTHA_BASE/cli:$VOLTHA_BASE/protos/third_party
diff --git a/python/adapters/protos/Makefile b/python/protos/Makefile
similarity index 100%
rename from python/adapters/protos/Makefile
rename to python/protos/Makefile
diff --git a/python/adapters/protos/__init__.py b/python/protos/__init__.py
similarity index 100%
rename from python/adapters/protos/__init__.py
rename to python/protos/__init__.py
diff --git a/python/adapters/protos/third_party/__init__.py b/python/protos/third_party/__init__.py
similarity index 96%
rename from python/adapters/protos/third_party/__init__.py
rename to python/protos/third_party/__init__.py
index 2740afe..1767870 100644
--- a/python/adapters/protos/third_party/__init__.py
+++ b/python/protos/third_party/__init__.py
@@ -38,7 +38,7 @@
def load_module(self, name):
if name in sys.modules:
return sys.modules[name]
- full_name = 'adapters.protos.third_party.' + name
+ full_name = 'python.protos.third_party.' + name
import_module(full_name)
module = sys.modules[full_name]
sys.modules[name] = module
diff --git a/python/adapters/protos/third_party/google/LICENSE b/python/protos/third_party/google/LICENSE
similarity index 100%
rename from python/adapters/protos/third_party/google/LICENSE
rename to python/protos/third_party/google/LICENSE
diff --git a/python/adapters/protos/third_party/google/__init__.py b/python/protos/third_party/google/__init__.py
similarity index 100%
rename from python/adapters/protos/third_party/google/__init__.py
rename to python/protos/third_party/google/__init__.py
diff --git a/python/adapters/protos/third_party/google/api/__init__.py b/python/protos/third_party/google/api/__init__.py
similarity index 100%
rename from python/adapters/protos/third_party/google/api/__init__.py
rename to python/protos/third_party/google/api/__init__.py
diff --git a/python/adapters/protos/third_party/google/api/annotations.proto b/python/protos/third_party/google/api/annotations.proto
similarity index 100%
rename from python/adapters/protos/third_party/google/api/annotations.proto
rename to python/protos/third_party/google/api/annotations.proto
diff --git a/python/adapters/protos/third_party/google/api/http.proto b/python/protos/third_party/google/api/http.proto
similarity index 100%
rename from python/adapters/protos/third_party/google/api/http.proto
rename to python/protos/third_party/google/api/http.proto