class Path(PurePosixPath):
    _flavour = APIFlavour()

    @classmethod
    def _from_parsed_parts(cls, drv, root, parts, init=True):
        if parts:
            parts = [root] + os.path.relpath(os.path.join(*parts),
                                             start=root).split(os.path.sep)
            parts = [p for p in parts if p not in (".", "")]
        return super(cls, Path)._from_parsed_parts(drv, root, parts, init)

    def __init__(self, *args):
        self.meta = {}

    @property
    def base(self):
        try:
            return self.parts[1]
        except IndexError:
            pass
        return ''

    @property
    def is_root(self):
        return len(self.parts) == 1 and self.root == "/"

    @property
    def is_fq_name(self):
        return not self.is_uuid

    @property
    def is_uuid(self):
        try:
            UUID(self.name, version=4)
        except (ValueError, IndexError):
            return False
        return True

    @property
    def is_resource(self):
        """The path is a resource if it is not a Collection

        :raises AbsPathRequired: path doesn't start with '/'
        """
        return not self.is_collection

    @property
    def is_collection(self):
        """The path is a Collection if there is only one part in the path. Be
        careful, the root is a Collection.

        :raises AbsPathRequired: path doesn't start with '/'
        """
        if not self.is_absolute():
            raise AbsPathRequired()
        return self.base == self.name

    def relative_to(self, path):
        try:
            return PurePosixPath.relative_to(self, path)
        except ValueError:
            return self class FQName(collections.Sequence):

    def __init__(self, init=None):
        if isinstance(init, string_types):
            self._data = init.split(':')
        elif isinstance(init, list):
            self._data = init
        elif isinstance(init, FQName):
            self._data = init._data
        else:
            self._data = []

    def __getitem__(self, idx):
        return self._data[idx]

    def __len__(self):
        return len(self._data)

    def __eq__(self, other):
        if isinstance(other, FQName):
            return self._data == other._data
        return self._data == other

    def __ne__(self, other):
        if isinstance(other, FQName):
            return self._data != other._data
        return self._data != other

    def __repr__(self):
        return repr(self._data)

    def __str__(self):
        return ':'.join(self._data)

    def __bytes__(self):
        return b(':'.join(self._data))

    def __lt__(self, b):
        return len(text_type(self)) < len(text_type(b))

    def __gt__(self, b):
        return not self.__lt__(b) class RootCollection(Collection):

    def __init__(self, **kwargs):
        return super(RootCollection, self).__init__('', **kwargs) class Collection(ResourceBase, UserList):
    """Class for interacting with an API collection

    >>> from contrail_api_cli.resource import Collection
    >>> c = Collection('virtual-network', fetch=True)
    >>> # iterate over the resources
    >>> for r in c:
    >>>     print(r.path)
    >>> # filter support
    >>> c.filter("router_external", False)
    >>> c.fetch()
    >>> assert all([r.get('router_external') for r in c]) == False

    :param type: name of the collection
    :type type: str
    :param fetch: immediately fetch collection from the server
    :type fetch: bool
    :param recursive: level of recursion
    :type recursive: int
    :param fields: list of field names to fetch
    :type fields: [str]
    :param filters: list of filters
    :type filters: [(name, value), ...]
    :param parent_uuid: filter by parent_uuid
    :type parent_uuid: v4UUID str or list of v4UUID str
    :param back_ref_uuid: filter by back_ref_uuid
    :type back_ref_uuid: v4UUID str or list of v4UUID str
    :param data: initial resources of the collection
    :type data: [Resource]
    """

    def __init__(self, type, fetch=False, recursive=1,
                 fields=None, detail=None, filters=None,
                 parent_uuid=None, back_refs_uuid=None,
                 data=None, session=None):
        super(Collection, self).__init__(session=session)
        UserList.__init__(self, initlist=data)
        self.type = type
        self.fields = fields or []
        self.filters = filters or []
        self.parent_uuid = list(self._sanitize_uuid(parent_uuid))
        self.back_refs_uuid = list(self._sanitize_uuid(back_refs_uuid))
        self.detail = detail
        if fetch:
            self.fetch(recursive=recursive)
        self.emit('created', self)

    @http_error_handler
    def __len__(self):
        """Return the number of items of the collection

        :rtype: int
        """
        if not self.data:
            params = self._format_fetch_params()
            res = self.session.get_json(self.href, count=True, **params)
            try:
                return res[self._contrail_name]['count']
            except KeyError:
                return 0
        return super(Collection, self).__len__()

    def __hash__(self):
        return hash(self.type)

    @property
    def _contrail_name(self):
        if self.type:
            return self.type + 's'
        return self.type

    def _sanitize_uuid(self, uuid):
        if uuid is None:
            raise StopIteration
        if isinstance(uuid, string_types):
            uuid = [uuid]
        for u in uuid:
            try:
                UUID(u, version=4)
            except ValueError:
                continue
            yield u

    def filter(self, field_name, field_value):
        """Add permanent filter on the collection

        :param field_name: name of the field to filter on
        :type field_name: str
        :param field_value: value to filter on

        :rtype: Collection
        """
        self.filters.append((field_name, field_value))
        return self

    def _format_fetch_params(self, fields=[], detail=False, filters=[],
                             parent_uuid=None, back_refs_uuid=None):
        params = {}
        detail = detail or self.detail
        fields_str = ",".join(self._fetch_fields(fields))
        filters_str = ",".join(['%s==%s' % (f, json.dumps(v))
                                for f, v in self._fetch_filters(filters)])
        parent_uuid_str = ",".join(self._fetch_parent_uuid(parent_uuid))
        back_refs_uuid_str = ",".join(self._fetch_back_refs_uuid(back_refs_uuid))
        if detail is True:
            params['detail'] = detail
        elif fields_str:
            params['fields'] = fields_str
        if filters_str:
            params['filters'] = filters_str
        if parent_uuid_str:
            params['parent_id'] = parent_uuid_str
        if back_refs_uuid_str:
            params['back_ref_id'] = back_refs_uuid_str

        return params

    def _fetch_parent_uuid(self, parent_uuid=None):
        return self.parent_uuid + list(self._sanitize_uuid(parent_uuid))

    def _fetch_back_refs_uuid(self, back_refs_uuid=None):
        return self.back_refs_uuid + list(self._sanitize_uuid(back_refs_uuid))

    def _fetch_filters(self, filters=None):
        return self.filters + (filters or [])

    def _fetch_fields(self, fields=None):
        return self.fields + (fields or [])

    @http_error_handler
    def fetch(self, recursive=1, fields=None, detail=None,
              filters=None, parent_uuid=None, back_refs_uuid=None):
        """
        Fetch collection from API server

        :param recursive: level of recursion
        :type recursive: int
        :param fields: fetch only listed fields.
                       contrail 3.0 required
        :type fields: [str]
        :param detail: fetch all fields
        :type detail: bool
        :param filters: list of filters
        :type filters: [(name, value), ...]
        :param parent_uuid: filter by parent_uuid
        :type parent_uuid: v4UUID str or list of v4UUID str
        :param back_refs_uuid: filter by back_refs_uuid
        :type back_refs_uuid: v4UUID str or list of v4UUID str

        :rtype: Collection
        """

        params = self._format_fetch_params(fields=fields, detail=detail, filters=filters,
                                           parent_uuid=parent_uuid, back_refs_uuid=back_refs_uuid)
        data = self.session.get_json(self.href, **params)

        if not self.type:
            self.data = [Collection(col["link"]["name"],
                                    fetch=recursive - 1 > 0,
                                    recursive=recursive - 1,
                                    fields=self._fetch_fields(fields),
                                    detail=detail or self.detail,
                                    filters=self._fetch_filters(filters),
                                    parent_uuid=self._fetch_parent_uuid(parent_uuid),
                                    back_refs_uuid=self._fetch_back_refs_uuid(back_refs_uuid))
                         for col in data['links']
                         if col["link"]["rel"] == "collection"]
        else:
            # when detail=False, res == {resource_attrs}
            # when detail=True, res == {'type': {resource_attrs}}
            self.data = [Resource(self.type,
                                  fetch=recursive - 1 > 0,
                                  recursive=recursive - 1,
                                  **res.get(self.type, res))
                         for res_type, res_list in data.items()
                         for res in res_list]

        return self class Resource(ResourceBase, UserDict):
    """Class for interacting with an API resource

    >>> from contrail_api_cli.resource import Resource
    >>> r = Resource('virtual-network',
                     uuid='4c45e89b-7780-4b78-8508-314fe04a7cbd',
                     fetch=True)
    >>> r['display_name'] = 'foo'
    >>> r.save()

    >>> p = Resource('project', fq_name='default-domain:admin')
    >>> r = Resource('virtual-network', fq_name='default-domain:admin:net1',
                     parent=p)
    >>> r.save()

    :param type: type of the resource
    :type type: str
    :param fetch: immediately fetch resource from the server
    :type fetch: bool
    :param uuid: uuid of the resource
    :type uuid: v4UUID str
    :param fq_name: fq name of the resource
    :type fq_name: str (domain:project:identifier)
                   or list ['domain', 'project', 'identifier']
    :param check: check that the resource exists
    :type check: bool
    :param parent: parent resource
    :type parent: Resource
    :param recursive: level of recursion
    :type recursive: int

    :raises ResourceNotFound: bad uuid or fq_name is given
    :raises HttpError: when save(), fetch() or delete() fail

    .. note::

        Either fq_name or uuid must be provided.
    """

    def __init__(self, type, fetch=False, check=False,
                 parent=None, recursive=1, session=None, **kwargs):
        assert('fq_name' in kwargs or 'uuid' in kwargs or 'to' in kwargs)
        super(Resource, self).__init__(session=session)
        self.type = type

        UserDict.__init__(self, kwargs)
        self.from_dict(self.data)

        if parent:
            self.parent = parent

        if check:
            self.check()

        if fetch:
            self.fetch(recursive=recursive)

        self.properties = {prop.key: prop for prop in self.schema.properties}
        self.emit('created', self)

    def __getattr__(self, attr):
        if attr in self.properties:
            return self.get(attr, self.properties[attr].default)
        msg = "'{0}' object has no attribute '{1}'"
        raise AttributeError(msg.format(type(self).__name__, attr))

    def __dir__(self):
        return sorted(set(dir(type(self)) + list(self.__dict__) +
                      self.properties.keys()))

    def __eq__(self, other):
        if not isinstance(other, Resource) or not self.type == other.type:
            return False
        if self.uuid != other.uuid:
            return False
        if self.fq_name != other.fq_name:
            return False
        return True

    def __hash__(self):
        return hash((self.type, self.uuid, str(self.fq_name)))

    @property
    def schema(self):
        return Context().schema.resource(self.type)

    def check(self):
        """Check that the resource exists.

        :raises ResourceNotFound: if the resource doesn't exists
        """
        if self.fq_name:
            self['uuid'] = self._check_fq_name(self.fq_name)
        elif self.uuid:
            self['fq_name'] = self._check_uuid(self.uuid)
        return True

    @http_error_handler
    def _check_uuid(self, uuid):
        return self.session.id_to_fqname(uuid, type=self.type)['fq_name']

    @http_error_handler
    def _check_fq_name(self, fq_name):
        return self.session.fqname_to_id(fq_name, self.type)

    @property
    def exists(self):
        """Returns True if the resource exists on the API server,
        or returns False.

        :rtype: bool
        """
        try:
            self.check()
        except ResourceNotFound:
            return False
        return True

    @property
    def uuid(self):
        """Return UUID of the resource

        :rtype: str
        """
        return self.get('uuid', super(Resource, self).uuid)

    @property
    def fq_name(self):
        """Return FQDN of the resource

        :rtype: FQName
        """
        return self.get('fq_name', self.get('to', super(Resource, self).fq_name))

    @property
    def parent(self):
        """Return parent resource

        :rtype: Resource
        :raises ResourceNotFound: parent resource doesn't exists
        :raises ResourceMissing: parent resource is not defined
        """
        try:
            return Resource(self['parent_type'], uuid=self['parent_uuid'], check=True)
        except KeyError:
            raise ResourceMissing('%s has no parent resource' % self)

    @parent.setter
    def parent(self, resource):
        """Set parent resource

        :param resource: parent resource
        :type resource: Resource

        :raises ResourceNotFound: resource not found on the API
        """
        resource.check()
        self['parent_type'] = resource.type
        self['parent_uuid'] = resource.uuid

    @property
    def created(self):
        """Return creation date

        :rtype: datetime
        :raises ResourceNotFound: resource not found on the API
        """
        if 'id_perms' not in self:
            self.fetch()
        created = self['id_perms']['created']
        return datetime.strptime(created, '%Y-%m-%dT%H:%M:%S.%f')

    @http_error_handler
    def save(self):
        """Save the resource to the API server

        If the resource doesn't have a uuid the resource will be created.
        If uuid is present the resource is updated.

        :rtype: Resource
        """
        if self.path.is_collection:
            self.session.post_json(self.href,
                                   {self.type: dict(self.data)},
                                   cls=ResourceEncoder)
        else:
            self.session.put_json(self.href,
                                  {self.type: dict(self.data)},
                                  cls=ResourceEncoder)
        return self.fetch(exclude_children=True, exclude_back_refs=True)

    @http_error_handler
    def delete(self):
        """Delete resource from the API server
        """
        res = self.session.delete(self.href)
        self.emit('deleted', self)
        return res

    @http_error_handler
    def fetch(self, recursive=1, exclude_children=False, exclude_back_refs=False):
        """Fetch resource from the API server

        :param recursive: level of recursion for fetching resources
        :type recursive: int
        :param exclude_children: don't get children references
        :type exclude_children: bool
        :param exclude_back_refs: don't get back_refs references
        :type exclude_back_refs: bool

        :rtype: Resource
        """
        if not self.path.is_resource and not self.path.is_uuid:
            self.check()
        params = {}
        # even if the param is False the API will exclude resources
        if exclude_children:
            params['exclude_children'] = True
        if exclude_back_refs:
            params['exclude_back_refs'] = True
        data = self.session.get_json(self.href, **params)[self.type]
        self.from_dict(data)
        return self

    def from_dict(self, data, recursive=1):
        """Populate the resource from a python dict

        :param recursive: level of recursion for fetching resources
        :type recursive: int
        """
        # Find other linked resources
        data = self._encode_resource(data, recursive=recursive)
        self.data = data

    def _encode_resource(self, data, recursive=1):
        for attr in ('fq_name', 'to'):
            if attr in data:
                data[attr] = FQName(data[attr])
        data = self.refs.encode(data, recursive)
        data = self.back_refs.encode(data, recursive)
        data = self.children.encode(data, recursive)
        return data

    @property
    def refs(self):
        """Return refs resources of the resource

        :rtype: LinkedResources
        """
        return LinkedResources(LinkType.REF, self)

    @property
    def back_refs(self):
        """Return back_refs resources of the resource

        :rtype: LinkedResources
        """
        return LinkedResources(LinkType.BACK_REF, self)

    @property
    def children(self):
        """Return children resources of the resource

        :rtype: LinkedResources
        """
        return LinkedResources(LinkType.CHILDREN, self)

    def remove_ref(self, ref):
        """Remove reference from self to ref

        >>> iip = Resource('instance-ip',
                           uuid='30213cf9-4b03-4afc-b8f9-c9971a216978',
                           fetch=True)
        >>> for vmi in iip['virtual_machine_interface_refs']:
                iip.remove_ref(vmi)
        >>> iip['virtual_machine_interface_refs']
        KeyError: u'virtual_machine_interface_refs'

        :param ref: reference to remove
        :type ref: Resource

        :rtype: Resource
        """
        self.session.remove_ref(self, ref)
        return self.fetch()

    def remove_back_ref(self, back_ref):
        """Remove reference from back_ref to self

        :param back_ref: back_ref to remove
        :type back_ref: Resource

        :rtype: Resource
        """
        back_ref.remove_ref(self)
        return self.fetch()

    def set_ref(self, ref, attr=None):
        """Set reference to resource

        Can be used to set references on a resource
        that is not already created.

        :param ref: reference to add
        :type ref: Resource

        :rtype: Resource
        """
        ref_attr = '%s_refs' % ref.type.replace('-', '_')
        ref = {
            'to': ref.fq_name,
            'uuid': ref.uuid,
        }
        if ref_attr in self:
            self[ref_attr].append(ref)
        else:
            self[ref_attr] = [ref]
        return self

    def add_ref(self, ref, attr=None):
        """Add reference to resource

        :param ref: reference to add
        :type ref: Resource

        :rtype: Resource
        """
        self.session.add_ref(self, ref, attr)
        return self.fetch()

    def add_back_ref(self, back_ref, attr=None):
        """Add reference from back_ref to self

        :param back_ref: back_ref to add
        :type back_ref: Resource

        :rtype: Resource
        """
        back_ref.add_ref(self, attr)
        return self.fetch()

    def json(self):
        """Return JSON representation of the resource
        """
        return to_json(self.data, cls=ResourceEncoder) class ResourceEncoder(json.JSONEncoder):

    def default(self, obj):
        if isinstance(obj, FQName):
            return obj._data
        if isinstance(obj, Resource):
            return obj.data
        if isinstance(obj, Collection):
            return obj.data
        return super(ResourceEncoder, self).default(obj) class ContrailAPISession(Session):
    user_agent = "contrail-api-cli"
    protocol = None
    host = None
    port = None
    base_uri = None
    default_headers = {
        'X-Contrail-Useragent': '%s:%s' % (platform.node(), 'contrail-api-cli'),
        "Content-Type": "application/json"
    }

    def __init__(self, host="localhost", port=8082, protocol="http", base_uri='', **kwargs):
        self.host = host
        self.port = port
        self.protocol = protocol
        self.base_uri = base_uri
        session = requests.Session()
        adapter = requests.adapters.HTTPAdapter(pool_connections=100, pool_maxsize=100)
        session.mount('http://', adapter)
        session.mount('https://', adapter)
        super(ContrailAPISession, self).__init__(session=session, **kwargs)

    @property
    def user(self):
        if hasattr(self.auth, 'username'):
            return self.auth.username
        else:
            return 'unknown'

    @property
    def base_url(self):
        return "%s://%s:%s%s" % (self.protocol, self.host, self.port, self.base_uri)

    def make_url(self, uri):
        return self.base_url + uri

    @contrail_error_handler
    def get_json(self, url, **kwargs):
        return self.get(url, params=kwargs).json()

    @contrail_error_handler
    def delete(self, *args, **kwargs):
        return super(ContrailAPISession, self).delete(*args, **kwargs)

    @contrail_error_handler
    def post_json(self, url, data, cls=None, **kwargs):
        """
        POST data to the api-server

        :param url: resource location (eg: "/type/uuid")
        :type url: str
        :param cls: JSONEncoder class
        :type cls: JSONEncoder
        """
        kwargs['data'] = to_json(data, cls=cls)
        kwargs['headers'] = self.default_headers
        return self.post(url, **kwargs).json()

    @contrail_error_handler
    def put_json(self, url, data, cls=None, **kwargs):
        """
        PUT data to the api-server

        :param url: resource location (eg: "/type/uuid")
        :type url: str
        :param cls: JSONEncoder class
        :type cls: JSONEncoder
        """
        kwargs['data'] = to_json(data, cls=cls)
        kwargs['headers'] = self.default_headers
        return self.put(url, **kwargs).json()

    def fqname_to_id(self, fq_name, type):
        """
        Return uuid for fq_name

        :param fq_name: resource fq name
        :type fq_name: FQName
        :param type: resource type
        :type type: str

        :rtype: UUIDv4 str
        :raises HttpError: fq_name not found
        """
        data = {
            "type": type,
            "fq_name": list(fq_name)
        }
        return self.post_json(self.make_url("/fqname-to-id"), data)["uuid"]

    def id_to_fqname(self, uuid, type=None):
        """
        Return fq_name and type for uuid

        If `type` is provided check that uuid is actually
        a resource of type `type`. Raise HttpError if it's
        not the case.

        :param uuid: resource uuid
        :type uuid: UUIDv4 str
        :param type: resource type
        :type type: str

        :rtype: dict {'type': str, 'fq_name': FQName}
        :raises HttpError: uuid not found
        """
        data = {
            "uuid": uuid
        }
        result = self.post_json(self.make_url("/id-to-fqname"), data)
        result['fq_name'] = FQName(result['fq_name'])
        if type is not None and not result['type'].replace('_', '-') == type:
            raise HttpError('uuid %s not found for type %s' % (uuid, type), http_status=404)
        return result

    def add_ref(self, r1, r2, attr=None):
        self._ref_update(r1, r2, 'ADD', attr)

    def remove_ref(self, r1, r2):
        self._ref_update(r1, r2, 'DELETE')

    def _ref_update(self, r1, r2, action, attr=None):
        data = {
            'type': r1.type,
            'uuid': r1.uuid,
            'ref-type': r2.type,
            'ref-uuid': r2.uuid,
            'ref-fq-name': list(r2.fq_name),
            'operation': action,
            'attr': attr
        }
        return self.post_json(self.make_url("/ref-update"), data)

    def search_kv_store(self, key):
        """Search for a key in the key-value store.
        :param key: string
        :rtype: string
        """
        data = {
            'operation': 'RETRIEVE',
            'key': key
        }
        return self.post_json(self.make_url("/useragent-kv"), data)['value']

    def get_kv_store(self):
        """Retrieve all key-value store elements.

        :rtype: [{key, value}] where key and value are strings.
        """
        data = {
            'operation': 'RETRIEVE',
            'key': None
        }
        return self.post_json(self.make_url("/useragent-kv"), data)['value']

    def add_kv_store(self, key, value):
        """Add a key-value store entry.

        :param key: string
        :param value: string
        """
        data = {
            'operation': 'STORE',
            'key': key,
            'value': value
        }
        return self.post(self.make_url("/useragent-kv"), data=to_json(data),
                         headers=self.default_headers).text

    def remove_kv_store(self, key):
        """Remove a key-value store entry.

        :param key: string
        """
        data = {
            'operation': 'DELETE',
            'key': key
        }
        return self.post(self.make_url("/useragent-kv"), data=to_json(data),
                         headers=self.default_headers).text class ResourceNotFound(NotFound):

    def __str__(self):
        if self.r is None:
            return "Resource not found"
        return "Resource %s not found" % self.r.path \
            if self.r.path.is_uuid \
            else "Resource %s/%s not found" % \
            (self.r.type, text_type(self.r.fq_name)) class ResourceMissing(Exception):
    pass class CollectionNotFound(NotFound):

    def __str__(self):
        if self.c is None:
            return "Collection not found"
        return "Collection %s not found" % self.c.path class ChildrenExists(Exists):

    def __str__(self):
        return "Children %s exists" % self._paths class BackRefsExists(Exists):

    def __str__(self):
        return "Back references from %s exists" % self._paths class IsSystemResource(Exists):

    def __str__(self):
        return "System resources %s cannot be changed" % self._paths class CLITest(unittest.TestCase):

    @mock.patch('contrail_api_cli.resource.Context.session')
    def setUp(self, mock_session):
        self.maxDiff = None
        self.BASE = "http://localhost:8082"
        mock_session.configure_mock(base_url=self.BASE)
        mock_session.get_json.return_value = {
            "href": self.BASE,
            "links": [
                {"link": {"href": self.BASE + "/foos",
                          "name": "foo",
                          "rel": "collection"}},
                {"link": {"href": self.BASE + "/bars",
                          "name": "bar",
                          "rel": "collection"}},
                {"link": {"href": self.BASE + "/foobars",
                          "name": "foobar",
                          "rel": "collection"}}
            ]
        }
        DummyResourceSchema()
        Context().schema = DummySchema()

    def tearDown(self):
        Context().schema = None
import unittest
import json
    import mock
    import unittest.mock as mock
from keystoneauth1.exceptions.http import HttpError
from contrail_api_cli.utils import Path, FQName
from contrail_api_cli.resource import RootCollection, Collection, Resource, ResourceEncoder
from contrail_api_cli.client import ContrailAPISession
from contrail_api_cli.exceptions import ResourceNotFound, ResourceMissing, CollectionNotFound, ChildrenExists, BackRefsExists, IsSystemResource
from .utils import CLITest
        self.assertEqual(r.uuid, 'ec1afeaa-8930-43b0-a60a-939f23a50724')
        self.assertEqual(r.path, Path('/foo/ec1afeaa-8930-43b0-a60a-939f23a50724'))

        with self.assertRaises(ResourceNotFound) as e:
            r = Resource('bar', fq_name='domain:bar:nofound', check=True)
            self.assertEqual(str(e), "Resource domain:bar:nofound doesn't exists")

    @mock.patch('contrail_api_cli.resource.Context.session')
    def test_resource_uuid_validation(self, mock_session):
        # bind original method to mock_session
        mock_session.id_to_fqname = ContrailAPISession.id_to_fqname.__get__(mock_session)
        mock_session.make_url = ContrailAPISession.make_url.__get__(mock_session)

        # called by id_to_fqname
        def post(url, json):
            if json['uuid'] == 'a5a1b67b-4246-4e2d-aa24-479d8d47435d':
                return {
                    'type': 'foo',
                    'fq_name': [
                        'domain',
                        'foo',
                        'uuid'
                    ]
                }
            else:
                raise HttpError(http_status=404)

        mock_session.post_json.side_effect = post
        r = Resource('foo', uuid='a5a1b67b-4246-4e2d-aa24-479d8d47435d', check=True)
        self.assertEqual(str(r.fq_name), 'domain:foo:uuid')
        self.assertEqual(r.path, Path('/foo/a5a1b67b-4246-4e2d-aa24-479d8d47435d'))
        with self.assertRaises(ResourceNotFound) as e:
            r = Resource('bar', uuid='d6e9fae3-628c-448c-bfc5-849d82a9a016', check=True)
            self.assertEqual(str(e), "Resource d6e9fae3-628c-448c-bfc5-849d82a9a016 doesn't exists")

    @mock.patch('contrail_api_cli.resource.Context.session')
    def test_resource_fetch(self, mock_session):
        mock_session.configure_mock(base_url=self.BASE)

        mock_session.fqname_to_id.side_effect = HttpError(http_status=404)
        r = Resource('foo', fq_name='domain:bar:foo')
        with self.assertRaises(ResourceNotFound):
            r.fetch()

        mock_session.fqname_to_id.side_effect = [
            "07eeb3c0-42f5-427c-9409-6ae45b376aa2"
        ]
        mock_session.get_json.return_value = {
            'foo': {
                'uuid': '07eeb3c0-42f5-427c-9409-6ae45b376aa2',
                'fq_name': ['domain', 'bar', 'foo']
            }
        }
        r = Resource('foo', fq_name='domain:bar:foo')
        r.fetch()
        self.assertEqual(r.uuid, '07eeb3c0-42f5-427c-9409-6ae45b376aa2')
        mock_session.get_json.assert_called_with(r.href)

        r.fetch(exclude_children=True)
        mock_session.get_json.assert_called_with(r.href, exclude_children=True)

        r.fetch(exclude_back_refs=True)
        mock_session.get_json.assert_called_with(r.href, exclude_back_refs=True)

    @mock.patch('contrail_api_cli.resource.Context.session')
    def test_resource_save(self, mock_session):
        mock_session.configure_mock(base_url=self.BASE)
        mock_session.fqname_to_id.return_value = '8240f9c7-0f28-4ca7-b92e-2f6441a0a6dd'
        mock_session.get_json.side_effect = [
            {
                'foo': {
                    'uuid': '8240f9c7-0f28-4ca7-b92e-2f6441a0a6dd',
                    'attr': 'bar',
                    'fq_name': ['domain', 'bar', 'foo']
                }
            },
            {
                'foo': {
                    'uuid': '8240f9c7-0f28-4ca7-b92e-2f6441a0a6dd',
                    'attr': 'foo',
                    'fq_name': ['domain', 'bar', 'foo']
                }
            }
        ]

        r1 = Resource('foo', fq_name='domain:bar:foo')
        r1['attr'] = 'bar'
        r1.save()
        mock_session.post_json.assert_called_with(self.BASE + '/foos',
                                                  {'foo': {
                                                      'attr': 'bar',
                                                      'fq_name': ['domain', 'bar', 'foo']
                                                  }},
                                                  cls=ResourceEncoder)
        self.assertEqual(r1['attr'], 'bar')

        r1['attr'] = 'foo'
        r1.save()
        mock_session.put_json.assert_called_with(self.BASE + '/foo/8240f9c7-0f28-4ca7-b92e-2f6441a0a6dd',
                                                 {'foo': {
                                                     'attr': 'foo',
                                                     'fq_name': ['domain', 'bar', 'foo'],
                                                     'uuid': '8240f9c7-0f28-4ca7-b92e-2f6441a0a6dd'
                                                 }},
                                                 cls=ResourceEncoder)
        self.assertEqual(r1['attr'], 'foo')

    @mock.patch('contrail_api_cli.resource.Context.session')
    def test_resource_parent(self, mock_session):
        mock_session.configure_mock(base_url=self.BASE)

        p = Resource('bar', uuid='57ef609c-6c9b-4b91-a542-26c61420c37b')
        r = Resource('foo', fq_name='domain:foo', parent=p)
        self.assertEqual(p, r.parent)

        mock_session.id_to_fqname.side_effect = HttpError(http_status=404)
        with self.assertRaises(ResourceNotFound):
            p = Resource('foobar', uuid='1fe29f52-28dc-44a5-90d0-43de1b02cbd8')
            Resource('foo', fq_name='domain:foo', parent=p)


        with self.assertRaises(ResourceMissing):