예제 #1
0
class DescriptorOnboarder(object):
    """ This class is responsible for onboarding descriptors using Restconf"""
    DESC_ENDPOINT_MAP = {
        NsdYang.YangData_Nsd_NsdCatalog_Nsd: "nsd-catalog/nsd",
        RwNsdYang.YangData_Nsd_NsdCatalog_Nsd: "nsd-catalog/nsd",
        VnfdYang.YangData_Vnfd_VnfdCatalog_Vnfd: "vnfd-catalog/vnfd",
        RwVnfdYang.YangData_Vnfd_VnfdCatalog_Vnfd: "vnfd-catalog/vnfd",
    }

    DESC_SERIALIZER_MAP = {
        NsdYang.YangData_Nsd_NsdCatalog_Nsd: convert.NsdSerializer(),
        RwNsdYang.YangData_Nsd_NsdCatalog_Nsd: convert.RwNsdSerializer(),
        VnfdYang.YangData_Vnfd_VnfdCatalog_Vnfd: convert.VnfdSerializer(),
        RwVnfdYang.YangData_Vnfd_VnfdCatalog_Vnfd: convert.RwVnfdSerializer(),
    }

    HEADERS = {"content-type": "application/vnd.yang.data+json"}
    TIMEOUT_SECS = 5
    AUTH = ('admin', 'admin')

    def __init__(self,
                 log,
                 host="127.0.0.1",
                 port=8008,
                 use_ssl=False,
                 ssl_cert=None,
                 ssl_key=None):
        self._log = log
        self._host = host
        self.port = port
        self._use_ssl = use_ssl
        self._ssl_cert = ssl_cert
        self._ssl_key = ssl_key

        self.timeout = DescriptorOnboarder.TIMEOUT_SECS

    @classmethod
    def _get_headers(cls, auth):
        headers = cls.HEADERS.copy()
        if auth is not None:
            headers['authorization'] = auth

        return headers

    def _get_url(self, descriptor_msg):
        if type(descriptor_msg) not in DescriptorOnboarder.DESC_SERIALIZER_MAP:
            raise TypeError("Invalid descriptor message type")

        endpoint = DescriptorOnboarder.DESC_ENDPOINT_MAP[type(descriptor_msg)]

        url = "{}://{}:{}/api/config/{}".format(
            "https" if self._use_ssl else "http",
            self._host,
            self.port,
            endpoint,
        )

        return url

    def _make_request_args(self, descriptor_msg, auth=None):
        if type(descriptor_msg) not in DescriptorOnboarder.DESC_SERIALIZER_MAP:
            raise TypeError("Invalid descriptor message type")

        serializer = DescriptorOnboarder.DESC_SERIALIZER_MAP[type(
            descriptor_msg)]
        json_data = serializer.to_json_string(descriptor_msg)
        url = self._get_url(descriptor_msg)

        request_args = dict(
            url=url,
            data=json_data,
            headers=self._get_headers(auth),
            auth=DescriptorOnboarder.AUTH,
            verify=False,
            cert=(self._ssl_cert, self._ssl_key) if self._use_ssl else None,
            timeout=self.timeout,
        )

        return request_args

    def update(self, descriptor_msg, auth=None):
        """ Update the descriptor config

        Arguments:
            descriptor_msg - A descriptor proto-gi msg
            auth - the authorization header

        Raises:
            UpdateError - The descriptor config update failed
        """
        request_args = self._make_request_args(descriptor_msg, auth)
        try:
            response = requests.put(**request_args)
            response.raise_for_status()
        except requests.exceptions.ConnectionError as e:
            msg = "Could not connect to restconf endpoint: %s" % str(e)
            self._log.error(msg)
            raise UpdateError(msg) from e
        except requests.exceptions.HTTPError as e:
            msg = "PUT request to %s error: %s" % (request_args["url"],
                                                   response.text)
            self._log.error(msg)
            raise UpdateError(msg) from e
        except requests.exceptions.Timeout as e:
            msg = "Timed out connecting to restconf endpoint: %s", str(e)
            self._log.error(msg)
            raise UpdateError(msg) from e

    def onboard(self, descriptor_msg, auth=None):
        """ Onboard the descriptor config

        Arguments:
            descriptor_msg - A descriptor proto-gi msg
            auth - the authorization header

        Raises:
            OnboardError - The descriptor config update failed
        """

        request_args = self._make_request_args(descriptor_msg, auth)
        try:
            response = requests.post(**request_args)
            response.raise_for_status()
        except requests.exceptions.ConnectionError as e:
            msg = "Could not connect to restconf endpoint: %s" % str(e)
            self._log.error(msg)
            raise OnboardError(msg) from e
        except requests.exceptions.HTTPError as e:
            msg = "POST request to %s error: %s" % (request_args["url"],
                                                    response.text)
            self._log.error(msg)
            raise OnboardError(msg) from e
        except requests.exceptions.Timeout as e:
            msg = "Timed out connecting to restconf endpoint: %s", str(e)
            self._log.error(msg)
            raise OnboardError(msg) from e
예제 #2
0
    def check_output(self, out_dir, archive=False):
        prev_dir = os.getcwd()
        os.chdir(out_dir)
        # Check the archives or directories are present
        dirs = os.listdir(out_dir)
        # The desc dirs are using uuid, so cannot match name
        # Check there are 3 dirs or files
        self.assertTrue(len(dirs) >= 3)

        try:
            count = 0
            for a in dirs:
                desc = None
                if archive:
                    if os.path.isfile(a):
                        self.log.debug("Checking archive: {}".format(a))
                        with tarfile.open(a, 'r') as t:
                            for m in t.getnames():
                                if m.endswith('.yaml')  or m.endswith('.yml'):
                                    # Descriptor file
                                    t.extract(m)
                                    self.log.debug("Extracted file: {}".format(m))
                                    desc = m
                                    break
                    else:
                        continue

                else:
                    if os.path.isdir(a):
                        self.log.debug("Checking directory: {}".format(a))
                        for m in os.listdir(a):
                            if m.endswith('.yaml')  or m.endswith('.yml'):
                                desc = os.path.join(a, m)
                                break

                if desc:
                    self.log.debug("Checking descriptor: {}".format(desc))
                    with open(desc, 'r') as d:
                        rest, ext = os.path.splitext(desc)
                        if '_vnfd.y' in desc:
                            vnfd = convert.VnfdSerializer().from_file_hdl(d, ext)
                            gen_desc = vnfd.as_dict()
                            if 'ping_vnfd.y' in desc:
                                exp_desc = self.exp_descs.ping_vnfd.as_dict()
                            elif 'pong_vnfd.y' in desc:
                                exp_desc = self.exp_descs.pong_vnfd.as_dict()
                            else:
                                raise Exception("Unknown VNFD descriptor: {}".
                                                format(desc))
                        elif '_nsd.y' in desc:
                            nsd = convert.NsdSerializer().from_file_hdl(d, ext)
                            gen_desc = nsd.as_dict()
                            exp_desc = self.exp_descs.ping_pong_nsd.as_dict()
                        else:
                            raise Exception("Unknown file: {}".format(desc))

                        # Compare the descriptors
                        self.compare_dict(gen_desc, exp_desc)

                        # Increment the count of descriptiors found
                        count += 1

            if count != 3:
                raise Exception("Did not find expected number of descriptors: {}".
                                format(count))
        except Exception as e:
            self.log.exception(e)
            raise e

        finally:
            os.chdir(prev_dir)
예제 #3
0
class RestconfDescriptorHandler(tornado.web.RequestHandler):
    DESC_SERIALIZER_MAP = {
        "nsd": convert.NsdSerializer(),
        "vnfd": convert.VnfdSerializer(),
    }

    class AuthError(Exception):
        pass

    class ContentTypeError(Exception):
        pass

    class RequestBodyError(Exception):
        pass

    def initialize(self, log, auth, info):
        self._auth = auth
        # The superclass has self._log already defined so use a different name
        self._logger = log
        self._info = info
        self._logger.debug('Created restconf descriptor handler')

    def _verify_auth(self):
        if self._auth is None:
            return None

        auth_header = self.request.headers.get('Authorization')
        if auth_header is None or not auth_header.startswith('Basic '):
            self.set_status(401)
            self.set_header('WWW-Authenticate', 'Basic realm=Restricted')
            self._transforms = []
            self.finish()

            msg = "Missing Authorization header"
            self._logger.error(msg)
            raise RestconfDescriptorHandler.AuthError(msg)

        auth_header = auth_header.encode('ascii')
        auth_decoded = base64.decodebytes(auth_header[6:]).decode()
        login, password = auth_decoded.split(':', 2)
        login = login
        password = password
        is_auth = ((login, password) == self._auth)

        if not is_auth:
            self.set_status(401)
            self.set_header('WWW-Authenticate', 'Basic realm=Restricted')
            self._transforms = []
            self.finish()

            msg = "Incorrect username and password in auth header: got {}, expected {}".format(
                (login, password), self._auth)
            self._logger.error(msg)
            raise RestconfDescriptorHandler.AuthError(msg)

    def _verify_content_type_header(self):
        content_type_header = self.request.headers.get('content-type')
        if content_type_header is None:
            self.set_status(415)
            self._transforms = []
            self.finish()

            msg = "Missing content-type header"
            self._logger.error(msg)
            raise RestconfDescriptorHandler.ContentTypeError(msg)

        if content_type_header != "application/vnd.yang.data+json":
            self.set_status(415)
            self._transforms = []
            self.finish()

            msg = "Unsupported content type: %s" % content_type_header
            self._logger.error(msg)
            raise RestconfDescriptorHandler.ContentTypeError(msg)

    def _verify_headers(self):
        self._verify_auth()
        self._verify_content_type_header()

    def _verify_request_body(self, descriptor_type):
        if descriptor_type not in RestconfDescriptorHandler.DESC_SERIALIZER_MAP:
            raise ValueError("Unsupported descriptor type: %s" %
                             descriptor_type)

        body = self.request.body
        bytes_hdl = io.BytesIO(body)

        serializer = RestconfDescriptorHandler.DESC_SERIALIZER_MAP[
            descriptor_type]

        try:
            message = serializer.from_file_hdl(bytes_hdl, ".json")
        except convert.SerializationError as e:
            self.set_status(400)
            self._transforms = []
            self.finish()

            msg = "Descriptor request body not valid"
            self._logger.error(msg)
            raise RestconfDescriptorHandler.RequestBodyError() from e

        self._info.last_request_message = message

        self._logger.debug("Received a valid descriptor request")

    def put(self, descriptor_type):
        self._info.last_descriptor_type = descriptor_type
        self._info.last_method = "PUT"

        try:
            self._verify_headers()
        except (RestconfDescriptorHandler.AuthError,
                RestconfDescriptorHandler.ContentTypeError):
            return None

        try:
            self._verify_request_body(descriptor_type)
        except RestconfDescriptorHandler.RequestBodyError:
            return None

        self.write("Response doesn't matter?")

    def post(self, descriptor_type):
        self._info.last_descriptor_type = descriptor_type
        self._info.last_method = "POST"

        try:
            self._verify_headers()
        except (RestconfDescriptorHandler.AuthError,
                RestconfDescriptorHandler.ContentTypeError):
            return None

        try:
            self._verify_request_body(descriptor_type)
        except RestconfDescriptorHandler.RequestBodyError:
            return None

        self.write("Response doesn't matter?")
예제 #4
0
class OnboardTestCase(tornado.testing.AsyncHTTPTestCase):
    DESC_SERIALIZER_MAP = {
        "nsd": convert.NsdSerializer(),
        "vnfd": convert.VnfdSerializer(),
    }

    AUTH = ("admin", "admin")

    def setUp(self):
        self._log = logging.getLogger(__file__)
        self._loop = asyncio.get_event_loop()

        self._handler_info = HandlerInfo()
        super().setUp()
        self._port = self.get_http_port()
        self._onboarder = onboard.DescriptorOnboarder(log=self._log,
                                                      port=self._port)

    def get_new_ioloop(self):
        return tornado.platform.asyncio.AsyncIOMainLoop()

    def get_app(self):
        attrs = dict(auth=OnboardTestCase.AUTH,
                     log=self._log,
                     info=self._handler_info)
        return tornado.web.Application([
            (r"/api/config/project/default/.*/(nsd|vnfd)",
             RestconfDescriptorHandler, attrs),
        ])

    def get_msg(self, desc=None):
        if desc is None:
            desc = NsdYang.YangData_Nsd_NsdCatalog_Nsd(id=str(uuid.uuid4()),
                                                       name="nsd_name")
        serializer = OnboardTestCase.DESC_SERIALIZER_MAP['nsd']
        jstr = serializer.to_json_string(desc, project_ns=False)
        self._desc = jstr
        hdl = io.BytesIO(str.encode(jstr))
        return serializer.from_file_hdl(hdl, ".json")

    def get_json(self, msg):
        serializer = OnboardTestCase.DESC_SERIALIZER_MAP['nsd']
        json_data = serializer.to_json_string(msg, project_ns=True)
        return json.loads(json_data)

    @rift.test.dts.async_test
    def test_onboard_nsd(self):
        nsd_msg = self.get_msg()
        yield from self._loop.run_in_executor(
            None,
            functools.partial(self._onboarder.onboard,
                              descriptor_msg=nsd_msg,
                              auth=OnboardTestCase.AUTH))
        self.assertEqual(self._handler_info.last_request_message,
                         self.get_json(nsd_msg))
        self.assertEqual(self._handler_info.last_descriptor_type, "nsd")
        self.assertEqual(self._handler_info.last_method, "POST")

    @rift.test.dts.async_test
    def test_update_nsd(self):
        nsd_msg = self.get_msg()
        yield from self._loop.run_in_executor(
            None,
            functools.partial(self._onboarder.update,
                              descriptor_msg=nsd_msg,
                              auth=OnboardTestCase.AUTH))
        self.assertEqual(self._handler_info.last_request_message,
                         self.get_json(nsd_msg))
        self.assertEqual(self._handler_info.last_descriptor_type, "nsd")
        self.assertEqual(self._handler_info.last_method, "PUT")

    @rift.test.dts.async_test
    def test_bad_descriptor_type(self):
        nsd_msg = NsdYang.YangData_Nsd_NsdCatalog_Nsd()
        with self.assertRaises(TypeError):
            yield from self._loop.run_in_executor(None, self._onboarder.update,
                                                  nsd_msg)

        with self.assertRaises(TypeError):
            yield from self._loop.run_in_executor(None,
                                                  self._onboarder.onboard,
                                                  nsd_msg)

    @rift.test.dts.async_test
    def test_bad_port(self):
        # Use a port not used by the instantiated server
        new_port = self._port - 1
        self._onboarder.port = new_port
        nsd_msg = self.get_msg()

        with self.assertRaises(onboard.OnboardError):
            yield from self._loop.run_in_executor(None,
                                                  self._onboarder.onboard,
                                                  nsd_msg)

        with self.assertRaises(onboard.UpdateError):
            yield from self._loop.run_in_executor(None, self._onboarder.update,
                                                  nsd_msg)

    @rift.test.dts.async_test
    def test_timeout(self):
        # Set the timeout to something minimal to speed up test
        self._onboarder.timeout = .1

        nsd_msg = self.get_msg()

        # Force the request to timeout by running the call synchronously so the
        with self.assertRaises(onboard.OnboardError):
            self._onboarder.onboard(nsd_msg)

        # Force the request to timeout by running the call synchronously so the
        with self.assertRaises(onboard.UpdateError):
            self._onboarder.update(nsd_msg)