{"activeVersionTag":"latest","latestAvailableVersionTag":"latest","collection":{"info":{"_postman_id":"8c5ddd5a-eb6a-4960-813d-627a2bf595cc","name":"XOS REST APIs","description":"Virtualizing services applies to Residential Subscribers via RCORD, Mobile providers via MCORD, and Enterprise solutions using the same SDN components in ECORD (which is why there are no endpoints owned exclusively by ECORD).\n\n**Key Players**: AT&T, Türk Telekom, Deutsche Telekom\n\n# Virtual Central Office\n\n<img src=\"https://eti-software.imgix.net/cord-hardware-domains-of-use.webp\">\n\nThe XOS project repo is here: [https://github.com/opencord/xos](https://github.com/opencord/xosHigh)\n\n# XOS\n\nXOS is a model-based platform for assembling, controlling, and composing services. It defines a _service control plane_ that is layered on top of a diverse collection of back-end service implementations, including VM-hosted VNFs, container-based micro-services, and SDN-based control programs that embed functionality in white-box switches.\n\nXOS is designed around the principle of Everything-as-a-Services (XaaS), making it an integral part of [CORD](http://opencord.org/)™(Central Office Re-architected as a Datacenter).\n\nThe CORD platform leverages SDN, NFV and Cloud technologies to build agile data centers for the network edge. Integrating multiple open source projects, CORD delivers a cloud-native, open, programmable, agile platform for network operators to create innovative services.\n\nXOS defines CORD’s architecture programmatically, with a set of models and invariants serving as a specification of the architecture, plus a _generative toolchain_ that translates the declarative specification into executable code.\n\nMoreover, as operators and developers gain experience with CORD, they are able to codify that experience in the models and invariants, making it possible to evolve the architecture over time.\n\nUse cases of XOS include [CORD](http://opencord.org/), a platform that combines NFV, SDN, and the elasticity of commodity clouds to bring datacenter economics and cloud agility to the Telco Central Office.\n\n# Terminology\n\nThis guide uses the following terminology. (Additional terminology and definitions can be found in an overview of the [CORD Ecosystem](https://wiki.opencord.org/display/CORD/Defining+CORD).)\n\n**POD** : A single physical deployment of CORD.\n\n**Full POD** : A typical configuration, used as example in this Guide. A full CORD POD consists of three servers and four fabric switches. This makes it possible to experiment with all the core features of CORD, and it is what the community typically uses for tests.\n\n**Half POD** : A minimum-sized configuration. It is similar to a full POD, but with less hardware. It consists of two servers (one head node and one compute node), and one fabric switch. It does not allow experimentation with all of the core features that CORD offers (e.g., a switching fabric), but it is still good for basic experimentation and testing.\n\n**CORD-in-a-Box (CiaB)** : Colloquial name for a Virtual POD.\n\n**Development (Dev) Machine** : This is the machine used to download, build and deploy CORD onto a POD. Sometimes it is a dedicated server, and sometimes the developer's laptop. In principle, it can be any machine that satisfies the hardware and software requirements.\n\n**Development (Dev) VM** : Bootstrapping the CORD installation requires a lot of software to be installed and some non-trivial configurations to be applied. All this should happen on the dev machine. To help users with the process, CORD provides an easy way to create a VM on the dev machine with all the required software and configurations in place.\n\n**Compute Node(s)** : A server in a POD that run VMs or containers associated with one or more tenant services. This terminology is borrowed from OpenStack.\n\n**Head Node** : A compute node of the POD that also runs management services. This includes for example XOS (the orchestrator), two instances of ONOS (the SDN controller, one to control the underlay fabric and one to control the overlay), MAAS and all the services needed to automatically install and configure the rest of the POD devices.\n\n**Fabric Switch** : A switch in a POD that interconnects other switches and servers inside the POD.\n\n**vSG** : The virtual Subscriber Gateway (vSG) is the CORD counterpart for existing CPEs. It implements a bundle of subscriber-selected functions, such as Restricted Access, Parental Control, Bandwidth Metering, Access Diagnostics and Firewall. These functions run on commodity hardware located in the Central Office rather than on the customer’s premises. There is still a device in the home (which we still refer to as the CPE), but it has been reduced to a bare-metal switch.\n\n# CORD - Central Office Re-architected as a Datacenter\n\n[Overview](https://guide.opencord.org/master/overview.html)\n\n## There are four major elements\n\n**Kubernetes**: All elements of the CORD control plane run in Kubernetes containers. CORD assumes a Kubernetes foundation (not shown above), but does not prescribe how Kubernetes or the underlying hardware are installed.\n\n**Platform**: The Platform layer consists of **ONOS**, **XOS**, **Kafka**, and collection of Logging and Monitoring micro-services, all running on a **Kubernetes** foundation. The platform is common to all Profiles.\n\n**Profile**: Each unique **CORD** configuration is defined by a Profile. It consists of a set of services (e.g., access services, VNFs, other cloud services), including both abstract services on-boarded into **XOS** and **SDN** control apps running on **ONOS**. Examples of profiles are SEBA and COMAC.\n\n**Workflow**: A Profile typically includes a workflow, which defines the business logic and state machine for one of the access technologies contained in the Profile. A workflow customizes a Profile for an operator's target deployment environment; it is not a software layer, per se. SEBA's AT&T Workflow is an example.\n\nThe diagram also shows a hardware bill-of-materials, which must be defined for a given POD.\n\n## Service Profile\n\n| **Service Profile** | **Description** |\n| --- | --- |\n| A-CORD | Analytics |\n| E-CORD | Enterprise Services |\n| M-CORD | Mobile Services |\n| R-CORD | Residential Services |\n\n# TOSCA Interface\n\nA TOSCA interface is available for configuring and controlling CORD. It is auto-generated from the set of [models](https://guide.opencord.org/master/xos/README.md) configured into the POD manifest, and includes both core and service-specific models.\n\n## What is TOSCA?\n\nTopology and Orchestration Specification for Cloud Applications (TOSCA) is an OASIS standard language to describe a topology of cloud based web services, their components, relationships, and the processes that manage them. The TOSCA standard includes specifications to describe processes that create or modify web services. You can read more about it on the [OASIS](https://www.oasis-open.org/committees/tc_home.php?wg_abbrev=tosca) website.\n\nCORD extends the TOSCA specification to support custom models for services, allow operators to manage them with a simple and well-known YAML interface.\n\n## Difference between `xos-tosca` and `\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*-tosca-loader`\n\nWhen you deploy CORD using helm charts you'll notice that there are two containers that contains the name `tosca`. There is quite a big difference between them:\n\n- `xos-tosca` contains the TOSCA engine and parser, and exposes a REST api to let you push TOSCA recipes into XOS\n- `\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*-tosca-loader` is a convenience container that pushes a set of recipes into `xos-tosca` and then exits.\n    \n\n# SEBA - SDN-Enabled Broadband Access\n\nTogether, SDN-Enabled Broadband Access (SEBA™) Reference Design and VIrtual OLT Hardware Abstraction (VOLTHA™) open source stack comprise ONF’s broadband access solution for carrier networks.\n\nSEBA is a Reference Design (RD) - an architecture that supports a multitude of virtualized access technologies at the edge of the carrier network, including PON, G-PON, G.Fast, and eventually DOCSIS and more. SEBA supports both residential access and wireless backhaul and is optimized such that traffic can run ‘fastpath’ straight through to the backbone without requiring VNF processing on a server. It's built with Kubernetes, high speed, and operationalized with FCAPS and OSS Integration. It serves as the foundational architecture for VOLTHA.\n\n# SEBA Profile\n\nSEBA (SDN Enabled Broadband Access) is a profile meant for residential access networks.\n\n<img src=\"https://guide.opencord.org/master/profiles/seba/seba-ep.png\">\n\nThe following pages describe how to install SEBA with an operator defined workflow, and how to configure, operate and troubleshoot an installed SEBA POD.\n\n# COMAC profile\n\nCOMAC (Converged Multi-Access and Core) is a profile for 5G mobile networks together with broadband access networks.\n\nHere is brief COMAC platform:\n\n<img src=\"https://guide.opencord.org/master/profiles/comac/images/COMAC_graphic.png\">\n\nNOTE: Among five elements in the figure, O-RAN Controller, and Broadband elements will be included at the future COMAC release.\n\nThe following pages describe how to install and configure COMAC and troubleshoot an installed COMAC PODs.\n\n# Architecture\n\n<img src=\"https://eti-software.imgix.net/CORD-High-Level-Architecture-e1535037945695.jpg\">\n\n## Simple Architecture\n\n<img src=\"https://eti-software.imgix.net/High-level-overview-of-the-CORD-architecture.png\" width=\"565\" height=\"503\">\n\n# SD-Fabric\n\nSoftware defined programmable fabric has many differentiating features.\n\n| **Feature** | **Description** |\n| --- | --- |\n| Right-sized Topology | Scale from smallest HA setup of a pair-of-ToRsto a full leaf-s\\[pine fabric (multiple racks_ as needed in edge or DC deployments |\n| API-Driven | Programmable API with ability to drop of reroute traffic, obtain telemetry and program application workloads across switches, CPUs and NICs. |\n| Cloud Native and Managed | Fully integrated and configured |\n| 5G as a Workload | –Tofino + BESS UPF. Scalable on demand  <br>–Smart NIC + BESS UPF extensions  <br>–5G slicing as primary construct |\n| Visibility | Visibility throughout the network while maintaining closed loop controls. |\n| Integration | K8s CNI and overlay enabling true end-to-end programmability and visibility. |\n\n# VOLTHA - Virtual OLT Hardware Abstraction\n\n<img src=\"https://eti-software.imgix.net/VOLTHA-and-Apps.png.webp\">\n\nFlexible and agile service adaptation at the cost of commodity servers and whitebox switches VOLTHA introduces the next-generation optical access system architecture, based on SDN/NFV technologies. Disaggregating PON functions to functional modules with open interfaces supports the CORD vision for open source reference implementations to service \"Access-as-a-Service\" use cases. VOLTHA is a virtual OLT hardware abstraction component that supports the CORD Project objective of multi-vendor, multi-domain \"any broadband access as a service\" reference implementation for the Central Office. VOLTHA provides isolation between an abstract (vendor agnostic) PON management system, and a set of vendor-specific and white-box PON hardware devices. On its north-bound interface, VOLTHA provides a set of abstract APIs which north-bound systems can interact with the PON networks. On its south-bound side, VOLTHA communicates with PON hardware devices using vendor-specific protocols and protocol extensions through adapters.\n\nThe documentation of VOLTHA is located here: [docs.voltha.org](https://docs.voltha.org/master/index.html)\n\n# vOLT and XOS\n\n<img src=\"https://eti-software.imgix.net//TB_vOLT01.jpg\">\n\n# vOLT Key Takeaways\n\nBelow is a summary table of some of the key takeaways or benefits which can be realized by using vOLT within the R-CORD and VOLTHA frameworks:\n\n- Fixed wireless Internet access. The Gigabit data rates of 5G mmWave could completely replace a number of Internet access technologies with hybrid fiber and wireless networks connecting subscriber homes. Although not truly a mobile system, it could provide competition to existing Wi-Fi systems that provide this type of fixed wireless access.\n- Outdoor urban/suburban small cells. An expected deployment scenario for 5G mmWave would be to provide increased capacity in high-demand public spaces and venues. With cell sizes around 100m, small 5G mmWave access points can be placed on poles or buildings to provide the required coverage.\n- Mission-critical control applications. Autonomous vehicles, vehicle-to-vehicle communications, drone communications, and other latency-sensitive, high-reliability applications provide another possible deployment scenarios for 5G mmWave with a projected network latency of less than a millisecond.\n- Indoor hotspot cells. Shopping malls, offices, and other indoor areas require a high-density of 5G mmWave micro cells. These small cells will potentially support download speeds of up to 20 Gbps, providing seamless access to cloud data and the ability to support multiple applications, as well as various forms of entertainment and multimedia.\n- Internet of Things. The general connectivity of objects, sensors, appliances and other devices for data collection, control, and analysis. Potentially could cover smart home applications, security, energy management, logistics and tracking, healthcare, and a multitude of other industrial operations.\n    \n\n|  | **Traditional PON** |   <br>  <br>**vOLT with VOLTHA**  <br>  <br> | **CORD Benefits** |\n| --- | --- | --- | --- |\n|   <br>  <br>Orchestration (XOS)(e.g. services added or subtracted)  <br>  <br> | Proprietary connectivity to OSS/BSS | Open Orchestration | Flexible and instant creation of virtual services. Demand and technology improvements can be kept up with by using virtual methods through the “cloudification” of the network (Services, subscriber gear (CPE) and OLT are all virtualized above the controller layer.) |\n| Management Platform(Proprietary vs. Open) | Proprietary Management Software | Open Control Software (ONOS) | Network centric view of data flow – data flows can be controlled with a bird’s eye view for optimal flows.Reduced OPEX using the same control platform across the entire network – the days of multiple different proprietary control platforms are over. |\n| Hardware Adapters(Proprietary vs. Open) | None – end-to-end proprietary software. | Open Common vOLTHA | Any underlying vOLT hardware can be used with vOLTHA – think of it as similar to a plug and play environment. |\n| Hardware(Proprietary Chassis vs. Open Commodity 1 RU Blades) | Proprietary Single Vendor Hardware | Open – Any Vendor Hardware | Hardware becomes a commodity – reduced CAPEX when hardware vendors produce generic PON equipment controlled through openFlow. |\n\n# BBSim, a Broadband Simulator\n\n## Quickstart\n\nBBSim (a.k.a. BroadBand Simulator) is a tool designed to emulate an [Openolt](https://github.com/opencord/openolt) compatible device.\n\nBBSim emulates the OLT, PON Ports, ONUs, UNIs and RG. For more informations on how configure different Services on the RG refer to [Configuring RG Services](https://docs.voltha.org/voltha-2.2/bbsim/docs/source/bbsim_config.html#configuringservices)\n\nIn order to use BBSim you need to have:\n\n- a Kubernetes cluster\n- helm\n- a working installation of VOLTHA\n    \n\nTo setup such an environment please look at the [voltha-helm-charts README](https://github.com/opencord/voltha-helm-charts/blob/master/README.md).\n\n## Installation\n\nOnce VOLTHA is up and running, you can deploy BBSim with this command:\n\n```\nhelm install -n voltha onf/bbsim --set olt_id=10\n\n```\n\nIf you need to specify a custom image for BBSim you can:\n\n```\nhelm install -n bbsim onf/bbsim --set images.bbsim.repository=bbsim --set images.bbsim.tag=candidate --set images.bbsim.pullPolicy=Never\n\n```\n\nThe BBSim installation can be customized to emulate multiple ONUs and multiple PON Ports:\n\n```\nhelm install -n voltha cord/bbsim --set onu=8 --set pon=2\n\n```\n\nBBSim can also be configured to automatically start Authentication or DHCP:\n\nOnce BBSim is installed you can verify that it’s running with:\n\n```\nkubectl logs -n voltha -f $(kubectl get pods -n voltha | grep bbsim | awk '{print $1}')\n\n```\n\n## Provision a BBSim OLT in VOLTHA\n\nCreate the device:\n\n```\nvoltctl device create -t openolt -H $(kubectl get -n voltha service/bbsim -o go-template='{{.spec.clusterIP}}'):50060\n\n```\n\nEnable the device:\n\n```\nvoltctl device enable $(voltctl device list --filter Type~openolt -q)\n\n```\n\n# XOS - REST API Docs\n\nXOS is a framework for operationalizing a collection of disaggregated services. It is packaged as a project in the Open CORD initiative, with source code managed through [https://gerrit.opencord.org](https://gerrit.opencord.org). It is also mirrored at [https://github.com/opencord](https://github.com/opencord).\n\nYou can download and use XOS either as part of CORD (see the CORD Guide for details) or as a standalone component (see the XOS Guide for details).\n\nAlso visit the XOS Wiki Page for additional information.\n\n# APIs\n\nThis section describes workflows for interacting with the API of the CORD. There are multiple APIs. Some of them are used in a Northbound context, for services sitting on top the CORD, and some are used internally for components to communicate with each other.\n\n- [gRPC](https://guide.opencord.org/master/xos/dev/grpc_api.html). The gRPC API is used internally for synchronizers and for Chameleon to speak with the XOS core. It's also available as a Northbound API.\n- [REST](https://guide.opencord.org/master/operating_cord/rest_apis.html). The REST API is implemented by the Chameleon container. In addition to being a popular Northbound API, it's also used by the XOS GUI.\n- [TOSCA](https://guide.opencord.org/master/xos-tosca/). TOSCA is implemented by the xos-tosca container and is typically used to configure and provision a POD. The following two references describe how to use TOSCA to configure Profiles and Services, respectively:\n    - [Configuring Profiles](https://guide.opencord.org/master/profiles/intro.html)\n    - [Configuring Services](https://guide.opencord.org/master/operating_cord/services.html)\n\nA RESTful interface is available for configuring and controlling CORD. It is auto-generated from the set of [models](https://guide.opencord.org/master/xos/intro.md) configured into a POD, and includes both core and service-specific models. Click [here](https://guide.opencord.org/master/api/xos/) to see API defined for the full set of services checked into [Gerrit](https://gerrit.opencord.org/).\n\nYou can access the REST API specification on a running POD by going to the `/apidocs/` URL on the Chameleon REST endpoint (exposed at port 30006 by default): `http://pod-ip-or-dns-address:30006/apidocs/`.\n\n[XOS API Guide](https://guide.xosproject.org/dev/xos-api.html)\n\n# Synchronizer Design\n\nSynchronizers act as the link between the data model and the functional half of the system. The data model contains a clean, abstract and declarative representation of the system curated by service developers and operators. This representation is not subject to the idiosyncrasies of distributed system behavior. It defines the authoritative state of the system. The functional half of the system, on the other hand, consists of the software that implements services along with the resources on which they run. Unlike the data model, its configuration is error-prone, liable to reach anomalous states, and involves mechanisms whose implementation and management sometimes do not follow best practices.\n\nA Synchronizer bridges these two sides of the system robustly through the use of an approach we call “goal-oriented synchronization.” Rather than tracking and relaying changes from the data model to the back-end system in the form of events, a synchronizer tracks and drives the system towards a final “goal state” corresponding to the current state defined by the data model. And it does so irrespective of the particular combination of changes that led to that state. As a consequence, an opportunity is made available at every step of synchronization to correct for anomalies created by prior steps, or ones that arise due to ambient system activity.\n\nThe specific method we use to accomplish this property is to require synchronization actions to be idempotent. This requirement boils down to two constraints on the implementation of a synchronizer. The first is for a synchronizer to compute a delta between the current and desired state of the service component it manages, and to then apply that delta. The second is to ensure that changes can never propagate back from the synchronizer to the data model in a way that affects the Synchronizer’s behavior. Of these, the first requirement is a burden on the service developer who implements a particular synchronizer, and the second requirement is fulfilled by the synchronizer core, which all service synchronizers share. The specific details of how the flow of details is kept unidirectional are provided in detail in later sections. For now, we will introduce the actors in a synchronizer that interact with the data model.\n\n## Actors and Types of State\n\nThere are four actors in a Synchronizer that interact with a Data Model:\n\n- **Synchronizer Actuators:** An actuator is notified of changes to the data model, upon which it refers to the current state of its service in the data model, and idempotently translates it into a configuration for that service. A given data model can only have one actuator, scheduled by the synchronizer core in an ordering consistent with the dependencies on the model that it synchronizes, with possible retries and error management.\n- **Model Policies:** A model policy encapsulates data relationships between related data models, such as “for every Network there must be at least one interface.” Concretely in this example, a model policy would intercept creations of Network models and create Interface models accordingly.\n- **Event Steps:** Event steps listen for external events and update the data model.\n- **Pull Steps:** Pull steps poll external components for state and update the data model.\n    \n\nThe Data Model represents the authoritative and abstract state of the system. By authoritative, we mean that if there is a conflict, then it is given precedence over the internal configuration of services. This state is a combination of two types of fields:\n\n- **Declarative:** Declarative state is sufficient to recreate the full operational state of the system, with the help of a particular synchronizer.\n- **Feedback:** Feedback state is derivative. It is the result of Synchronizer actions, preserved as a cache for later accesses to the backend objects created as a consequence of those actions.\n    \n\nSynchronizers are mainly interested in declarative state, as that is the basis on which they configure the service they implement. The core synchronizer machinery ensures that synchronizers are notified of changes to declarative state, that they are invoked in an appropriate order, and also provide a degree of resilience to failure.\n\nThe actors of a synchronizer interact with this state in the following manner:\n\n- Actuators can:\n    - Read Declarative state\n    - Read/Write Feedback state\n    - Be scheduled upon changes to Declarative state\n- Model Policies can\n    - Read/Write Declarative state\n    - Subscribe to changes to Declarative state\n- Event Steps and Pull Steps can\n    - Read/Write Declarative state\n    - Read/Write Feedback state\n\n## Relationships Between Synchronizers and Models\n\nA single synchronizer can synchronize multiple data models, usually through an actuator per model. However, a given model can only be handled by one actuator. Furthermore, a single actuator only synchronizes one data model. The act of synchronizing may generate feedback state in the same model, but watching never generates/modifies feedback state in the model being watched. (Watching model A may be part of synchronizing mode B, and so generates feedback state in B.)\n\nBut how are these relationships established? The answer lies in the linkages between models in the data model. The data model, which is implemented using Django, lets us link one model to another through references called foreign keys and many-to-many keys. Apart from enabling organizational patterns such as aggregation, composition, proxies, etc. this linkage is used to establish two levels of dependencies: ones between models, and ones between objects. If a field interface in a model for a daemon references an Interface model, then it implies that the daemon’s model depends on interface. Furthermore, that an object of type daemon depends on an object of type interface if the interface field of the latter contains a reference to the latter.\n\nDependencies between models can be specified in two ways:\n\n- Implicitly through linkages in the data model\n- Explicitly through annotations, which are in turn read by the synchronizer core\n    \n\nOnce these dependencies have been extracted, they configure the scheduling of actuators in a way that they are run in dependency order, and so that errors in the execution of an actuator are propagated to its dependencies. Consider the diagram below.\n\n## Loops\n\nThe separation of declarative and feedback state in the data model eliminates the possibility of loops involving actions, caused by a synchronizer directly modifying its declarative state. Such loops involve repeated executions of one or more actions by the synchronizer core. But it does not eliminate loops of the following kind\n\n1. Loops caused because a synchronizer modifies declarative state indirectly - say by triggering an external action that modifies the state via the API.\n2. Loops in which feedback state written by one Synchronizer is read by a second Synchronizer, and feedback state written by the second Synchronizer is read by the first Synchronizer. Of course, this type of interference can also happen across a chain of Synchronizers.\n3. Spin loops and other general loops found in programs.\n    \n\nThe second possibility is unlikely in practice because it would be akin to a data model version of a layering violation: Layer _i_ depends on Layer _i+1_, while at the same time Layer _i+1_ would depend on Layer _i_.\n\n## Dependencies and Data Consistency\n\nXOS enforces sequential consistency, without real-time bounds. This is to say that no guarantees are made on when the goal state will be transferred from the data model to the back end, but it is guaranteed that the components of the states defined by individual data models will be actuated in a valid order. This order is implied by the dependencies described in the previous section. For example, if a host model depends on an interface model, then it is guaranteed that the actuator of a host will execute only when the actuator of the corresponding interface has completed successfully.\n\nOutside of the ordering mandated by dependencies in the data model, operations may be rearranged randomly, or to favor the concurrent scheduling of actuators. This property poses an important task for a service designer, making it necessary for him to specify all ordering constraints comprehensively in the service data model. If any orderings are missed, then even if changes to a set of models are properly ordered at the source, their actuation may be reordered into sequences that are invalid.\n\n## Error Handling and Idempotence\n\nThe synchronizer is designed to be robust to unforeseeable faults in the back-end system. The main source of this robustness is the idempotence of actuators. Rather than blindly executing an operation on the current state, actuators target a goal state. This means that they are expected to make a reasonable effort to compensate for anomalies. Goal-directed synchronization, i.e., the strategy of driving towards the end state, rather than simply “replaying” events is central to this outcome. In the latter case, actuators would have no other choice than to dutifully apply incoming updates, even if the start state is anomalous, and likely lead to an anomalous end state.\n\nA synchronizer tries to schedule as many actuators as it can concurrently without violating dependencies. Dependencies are tracked at the object level. For example, in the example mentioned previously, the failure of the synchronization of an interface would hold up a host if the interface is bound to it, but not if that interface is bound to a different node. When there is a failure, the synchronizer core re-executes the actuator at a later time, and then again at increasing intervals.\n\n## Timestamps\n\nXOS models come with a variety of timestamps. The first three timestamps indicate changes that occur to the models:\n\n- `Updated`. Updated is set whenever a model is saved by a non-synchronizer. For example, updating a model via the GUI or the REST API will cause the updated timestamp to be set. The updated timestamp is set regardless of whether or not any actual changes have occurred to the model. This allows a developer or operator to save a model and cause the model to be resynchronized.\n- `changed_by_step`. This timestamp is set whenever non-bookkeeping fields in the model are changed during the execution of a syncstep. If no changes occur during a save, then this timestamp is not set.\n- `changed_by_policy`. This timestamp is set whenever non-bookkeeping fields in the model are changed during the execution of a model policy. If no changes occur during a save, then this timestamp is not set.\n    \n\nFor a given model, if we take the maximum of the three timestamps, `max(model.updated, model.changed_by_step, model.changed.by_policy)`, we can use that calculation as an overall version of the substantive fields of the model. If a user updated the model, or if a policy or syncstep changed the model, then one of those timestamps will be updated.\n\nThe following two timestamps are set when a sync or a model_policy is completed.\n\n- `enacted`. Enacted indicates the model has been successfully synced. It is set to `max(model.updated, model.changed_by_policy)`. The enacted timestamp does not indicate the time of the synchronization, but rather indicates the version of the data that was synchronized.\n- `policed`. Policed indicates the model has successfully had model policies applied. It is set to `max(model.updated, model.changed_by_step)`. The policed timestamp does not indicate the time the policy completed, but rather indicates the version of the data that had policies applied.\n    \n\nThe rules for running steps and policies are as follows:\n\n- Model policies are run if `model.updated > model.policed || model.changed_by_step > model.policed`. In other words, if a user updates the model, or a syncstep changes the model, then policies will run.\n- Sync steps are run if `model.udpated > model.enacted || model.changed_by_policy > model.enacted`. In other words, if a user updates the model, or a policy changes the model, then steps will be run.\n    \n\nThis means it is possible for a syncstep to trigger a policy, and it is possible for a policy to trigger a syncstep. A cycle is not necessarily bad assuming the cycle does eventually terminate in a steady state. Because the `changed_by_` timestamps are only set when a model changes (i.e. authoritative state change), and not merely when it is saved, simply no longer making changes to a model will break any cycle. It's recommended that developers do exercise caution when modifying models from both syncsteps and policies.\n\n# Modeler's Guide\n\nThis is also a **work-in-progress** documentation capturing current models for XOS and CORD. It's currently _exploratory_ and contains free-flowing commentary regarding observations from reviewing the originating reference code repository as well as any deviations and recommendations on expressing them as YANG models.\n\nThe initial effort is centered around capturing the CORD Subscriber model and its dependent models. We'll be reguarly updating this document as we capture additional models from XOS/CORD repository in the coming days.\n\n## XOS Data Models\n\n### xos-core\n\n- YANG schema: [xos-core.yang](https://github.com/opencord/composer/blob/master/schema/xos-core.yang)\n- Source reference: [opencord/xos](http://github.com/opencord/xos)\n    \n\nThis YANG module is the **primary** module that will house all XOS related data models going forward. The models for these came from `xos/core/models` directory in the XOS repository. It will eventually house the `Service` class, the `Tenant` class, etc. One notable convention here is the existence of the `/api/tenant` and `/api/service` configuration tree inside this module. In a sense, we're considering this YANG module to be the **master** module that all other modules derive from and augments into this module. You can see this _augment_ behavior in the [cord-core.yang](https://github.com/opencord/composer/blob/master/schema/cord-core.yang) schema.\n\nAs we capture more XOS data models, we will likely organize the additional models as separate YANG modules, such as `xos-service`, `xos-tenant`, `xos-slice`, etc. which will be _imported_ by this module.\n\nFor now, we've captured the `TenantRoot` and `Subscriber` classes.\n\nThis module contains basic placeholder configuration data tree and passed into `yang-express`.\n\n## CORD Data Models\n\n### cord-core\n\n- YANG schema: [cord-core.yang](https://github.com/opencord/composer/blob/master/schema/cord-core.yang)\n- Source reference: [opencord](http://github.com/opencord)\n    \n\nThis YANG module is the **primary** module that will house all CORD related data models going forward. It currently captures the `cord-subscriber` configuration tree and _imports_ the subscriber schema from the `cord-subscriber` YANG module. From a pure data modeling perspective, the current originating reference implementation has yet to achieve a clean functional separation between XOS and CORD. This will be a key area of focus as we attempt to define a clear degree of abstraction between the XOS models and CORD models.\n\nThis module contains subscriber-related configuration data tree and passed into `yang-express`.\n\nThere are two main entry-points on the `subscriber` instances. I've defined a `list subscriber` construct directly in the `module cord-core` which basically uses the `grouping subscriber-controller` data model. This means that the `cord-core` YANG module itself will be the authorative holder of all subscriber instances. Subsequently, I've augmented the `xos` module at the `/api/tenant` configuration tree to have a new `cord` tenant container along with a `node:link` to the subscriber list within the `cord-core` YANG module. This convention makes it possible to access the `subscriber` instances by directly accessing the `cord-core` module, such as `/cord-core:subscriber` or via the `xos` module, such as `/xos-core:api/tenant/cord/subscriber`.\n\n### cord-device\n\n- YANG schema: [cord-device.yang](https://github.com/opencord/composer/blob/master/schema/cord-device.yang)\n- Source reference: [opencord/olt](http://github.com/opencord/olt)\n    \n\nThis YANG module is based on the `CordDevice` class found inside the `subscriber.py` (API) module implementation. I think this particular model is rather under-developed and currently not placed in the right place (shouldn't be inside `subscriber.py` which should really just be the controller definitions). This module has a potential to be leveraged more effectively if the goal is for this to become one the the core CORD models from which other devices inherit from (which I'm guessing it will be).\n\nWe will need to review its association with the `cord-subscriber` model and better understand its role in relation with other _device_ oriented data models.\n\nThis module provides _export definitions_ and does not contain any configuration data tree.\n\n### cord-subscriber\n\n- YANG schema: [cord-subscriber.yang](https://github.com/opencord/composer/blob/master/schema/cord-subscriber.yang)\n- Source reference: [opencord/olt](http://github.com/opencord/olt)\n    \n\nThis module contains the heart of the initial modeling exercise. It captures the CORD Subscriber data model (which extends XOS Subscriber model, which extends XOS TenantRoot model). There are two primary models: `grouping subscriber` and `grouping subscriber-controller`.\n\nThis module provides _export definitions_ and does not contain any configuration data tree.\n\n#### grouping subscriber\n\nThe 'subscriber' model extends the `xos:subscriber` (from the [xos-core.yang](https://github.com/opencord/composer/blob/master/schema/xos-core.yang)) and mirrors as closely as possible the `CordSubscriberRoot` class. Some deviations were largely around the variable name convention where I've replaced all underscore with a dash (_upload_speed_ is now _upload-speed_). This is largely to comply with YANG convention where the schema is used to model XML element structure and underscores are not really used in XML based representations (not even sure if it is valid...).\n\nThe other key deviation is in the organization of the various attributes. Instead of having a simple flat list of properties, I've grouped them into related 'services' (pseudo-JSON below):\n\n``` json\nservices: {\n  cdn: {\n    enabled: true\n  },\n  firewall: {\n    enabled: true,\n    rules: []\n  },\n  url-filter: {\n    enabled: true,\n    level: 'PG',\n    rules: []\n  }\n  uverse: {\n    enabled: true\n  }\n}\n\n```\n\nEventually, I think these four _hard-coded_ services will be moved out of the `cord-subscriber` data model altogether. I'm not sure what _on-boarded_ services actually implement these features but it should be augmented by those individual service's YANG models into the cord-subscriber data model.\n\n#### grouping subscriber-controller\n\nI've internally debated creating this controller model because I thought that the necessary attributes were rather effectively captured in the prior `grouping subscriber` schema definition. But the presence of the `related` object container in the controller (that shouldn't be in the underlying model) convinced me to model it according to the `CordSubscriberNew` class found inside `api/tenant/cord/subscriber.py`. This `subscriber-controller` model extends the `subscriber` model (inheriting all its attributes) and introduces the additional containers for `features`, `identity`, and `related`. Since the main function of the `subscriber-controller` model is to essentially layer a _view-like_ overlay on top of the underlying cord-subscriber model, I've introduced a new _custom extension_ construct called `node:link`. This is to capture the fact that the various attributes being expressed are simply a reference link to the exact same data that is located at a different place within the same object.\n\nOne note on the `related` object, it is currently a placeholder container and I expect it will remain that way as part of the model. The reason is, the attributes that are currently mapped inside all come from the `VOLT` service (which then has other attributes from `VSG` service, etc.). When we get to the step of modeling the various `Service` entities, we'll capture the necessary _augment_ behavior in those separate YANG modules (i.e. cord-service-volt.yang, cord-service-vsg.yang, etc.).\n\n# Synchronizer Reference\n\nThis section is a reference for the commonly used APIs exposed by the synchronizer framework.\n\n## Convenience Methods\n\nAs part of the model definition, it is possible to extend the autogenerated gRPC APIs with custom methods, for example, to facilitate access to some kind of data from the synchronizers.\n\n`convenience_methods` can be defined in a folder (`convenience`) that needs to be a child of the `models_dir` as per your configuration.\n\nFor example if your configuration contains:\n\n```\nmodels_dir: \"/opt/xos/synchronizers//models\"\n\n```\n\nthen you `convenience methods` needs to be located in `/opt/xos/synchronizers//models/convenience`\n\nAssuming our model definition looks like:\n\n```\nmessage MyModel (XOSBase){\n    required string first_name = 1 [null = False, blank = False];\n    required string last_name = 2 [null = False, blank = False];\n}\n\n```\n\nhere is an example of a basic convenience methods that will expose a `full_name` property over the APIs used by the synchronizers:\n\n```\nfrom xosapi.orm import ORMWrapper, register_convenience_wrapper\nfrom xosconfig import Config\nfrom multistructlog import create_logger\nlog = create_logger(Config().get('logging'))\nclass ORMWrapperMyModel(ORMWrapper):\n    @property\n    def full_name(self):\n        return \"%s %s\" % (self.first_name, self.last_name)\nregister_convenience_wrapper(\"MyModel\", ORMWrapperMyModel)\n\n```\n\n**Note:** The convenience methods will be loaded in all the synchronizer containers so that they can be used in multiple places.\n\n## Model Policies\n\nModel Policies can be seen as `post-save` hooks and they are generally defined in the `xos/synchronizer/model_policies` folder of your service.\n\nModel policies are generally used to dynamically create a service chain (when a ServiceInstance is created it will create a ServiceInstance of its east side Service).\n\n**Note:** You'll need to add this folder in your synchronizer configuration file as:\n\n```\nmodel_policies_dir: \"/opt/xos/synchronizers//model_policies\"\n\n```\n\nA model policy is a class that inherits from `Policy`:\n\n```\nfrom synchronizers.new_base.modelaccessor import MyServiceInstance, ServiceInstanceLink, model_accessor\nfrom synchronizers.new_base.policy import Policy\nclass MyServiceInstancePolicy(Policy):\n    model_name = \"MyServiceInstance\"\n\n```\n\nand overrides one or more of the following methods:\n\n```\ndef handle_create(self, model):\n\n```\n\n```\ndef handle_update(self, model):\n\n```\n\n```\ndef handle_delete(self, model):\n\n```\n\nWhere `model` is the instance of the model that has been created.\n\n## Sync Steps\n\nSync Steps are the actual piece of code that provide the mapping between your models and your backend. You will need to define a sync step for each model.\n\n**Note:** You'll need to add this folder in your synchronizer configuration file as:\n\n```\nsteps_dir: \"/opt/xos/synchronizers//steps\"\n\n```\n\nA Sync Step is a class that inherits from `SyncStep`:\n\n```\nfrom synchronizers.new_base.SyncInstanceUsingAnsible import SyncStep\nfrom synchronizers.new_base.modelaccessor import MyModel\nfrom xosconfig import Config\nfrom multistructlog import create_logger\nlog = create_logger(Config().get('logging'))\nclass SyncMyModel(SyncStep):\n    provides = [MyModel]\n    observes = MyModel\n\n```\n\nand provides these methods:\n\n```\ndef sync_record(self, o):\n    log.info(\"sync'ing object\", object=str(o), **o.tologdict())\n\n```\n\n```\ndef delete_record(self, o):\n    log.info(\"deleting object\", object=str(o), **o.tologdict())\n\n```\n\nThis methods will be invoked anytime there is change in the model passing as argument the changed models. After performing the required operations to sync the model state with the backend state the synchronizer framework will update the models with the operational informations needed.\n\n## Pull Steps\n\nPull Steps can be used to observe the surrounding environment and update the data-model accordingly.\n\n**Note:** You'll need to add this folder in your synchronizer configuration file as:\n\n```\npull_steps_dir: \"/opt/xos/synchronizers//pull_steps\"\n\n```\n\nA Sync Step is a class that inherits from `PullStep`\n\n```\nfrom synchronizers.new_base.pullstep import PullStep\nfrom synchronizers.new_base.modelaccessor import OLTDevice\nfrom xosconfig import Config\nfrom multistructlog import create_logger\nlog = create_logger(Config().get('logging'))\nfrom synchronizers.new_base.modelaccessor import MyModel\nclass MyModelPullStep(PullStep):\n    def __init__(self):\n        super(MyModelPullStep, self).__init__(observed_model=OLTDevice)\n\n```\n\nand override the following method:\n\n```\ndef pull_records(self):\n    log.info(\"pulling MyModels\")\n    # create an empty model\n    o = MyModel()\n    # code to fetch information\n    # populate the model\n    o.first_name = \"John\"\n    o.last_name = \"Doe\"\n    o.no_sync = True # this is required to prevent the synchronizer to be invoked and start a loop\n    o.save()\n\n```\n\n## Event Steps\n\nEvent Steps are similar to pull steps in that they are often used to implement a flow of information from the environment into the data model. However, rather than using polling, event steps rely on externally generated events delivered via an event bus, such as Kafka.\n\n**Note:** You'll need to add this folder in your synchronizer configuration file as:\n\n```\nevent_steps_dir: \"/opt/xos/synchronizers//event_steps\"\n\n```\n\nYou'll also need to make sure the event bus endpoint is specified in the synchronizer config file. For example:\n\n```\nevent_bus:\n  endpoint: cord-kafka\n  kind: kafka\n\n```\n\nAn event step inherits from the `EventStep` class:\n\n```\nimport json\nfrom synchronizers.new_base.eventstep import EventStep\nfrom synchronizers.new_base.modelaccessor import MyModel\nfrom xosconfig import Config\nfrom multistructlog import create_logger\nlog = create_logger(Config().get('logging'))\nclass MyModelEventStep(EventStep):\n    technology = \"kafka\"\n    topics = [\"MyTopic\"]\n    def __init__(self, *args, **kwargs):\n        super(MyEventStep, self).__init__(*args, **kwargs)\n\n```\n\nTwo important class members that are defined in each event step are `technology` and `topics`. `technology` tells what type of event bus to use. There's currently only one bus interface implemented by the synchronizer framework, and that is `kafka`. The `topics` member is a list of topics that will be listened on for events. The precise meaning of `topics` is left to the particular event bus technology that is in use.\n\nService-specific logic is implemented by overriding the `process_event()` method:\n\n```\n    def process_event(self, event):\n        value = json.loads(event.value)\n        first_name = value[\"first_name\"]\n        last_name = value[\"last_name\"]\n        # See if the object already exists\n        objs = MyModel.filter(first_name=first_name, last_name=last_name)\n        if objs:\n            return\n        # Create a new object\n        obj = MyModel()\n        obj.first_name = first_name\n        obj.last_name = last_name\n        obj.save(always_update_timestamp = True)\n\n```\n\nIn this example we've made the assumption that the value of an event is a json-encoded dictionary containing the keys `first_name` and `last_name`. The event step in this case checks to see if an object with those fields already exists, and if not then it creates the object.\n\nIn this example, we've differed from the Pull Step example in that we omitted `no_sync=True` and we added `always_update_timestamp = True` to the `save()` call. This has the effect of causing the synchronizer to excute any sync steps that might exist for `MyModel`. Whether or not you want sync_steps to run is an implementation decision and depends upon the design of your synchronizer.\n\nSending an event to Kafka can be done using a variety of Kafka clients for various languages, Kafka command-line tools, etc. A python example is as follows:\n\n```\nimport json\nfrom kafka import KafkaProducer\nproducer = KafkaProducer(bootstrap_servers=\"cord-kafka\")\nproducer.send(\"MyTopic\", json.dumps({\"first_name\": \"John\", \"last_name\": \"Doe\"}))\nproducer.flush()\n\n```\n\n### Contact Support:\n\nEmail: [i](mailto:cord-dev@opencord.org)[nfo@etisoftware.com](mailto:nfo@etisoftware.com)","schema":"https://schema.getpostman.com/json/collection/v2.0.0/collection.json","isPublicCollection":false,"owner":"11528456","team":1088250,"collectionId":"8c5ddd5a-eb6a-4960-813d-627a2bf595cc","publishedId":"2s93z9cNYg","public":true,"publicUrl":"https://xos.etisoftware.com","privateUrl":"https://go.postman.co/documentation/11528456-8c5ddd5a-eb6a-4960-813d-627a2bf595cc","customColor":{"top-bar":"FFFFFF","right-sidebar":"303030","highlight":"FF6C37"},"documentationLayout":"classic-double-column","customisation":{"metaTags":[{"name":"description","value":""},{"name":"title","value":""}],"appearance":{"default":"light","themes":[{"name":"dark","logo":null,"colors":{"top-bar":"212121","right-sidebar":"303030","highlight":"FF6C37"}},{"name":"light","logo":null,"colors":{"top-bar":"FFFFFF","right-sidebar":"303030","highlight":"FF6C37"}}]}},"version":"8.10.1","publishDate":"2023-07-21T15:04:47.000Z","activeVersionTag":"latest","documentationTheme":"light","metaTags":{"title":"","description":""},"logos":{"logoLight":null,"logoDark":null}},"statusCode":200},"environments":[{"name":"ETI-Environment","id":"ccd84c46-e859-40f6-be77-828ec604591a","owner":"11528456","values":[{"key":"token","value":"","enabled":true},{"key":"baseUrl","value":"triad60.dev.etisoftware.com","enabled":true,"type":"default"},{"key":"instance","value":"eti-demo-maxicom","enabled":true,"type":"default"}],"published":true}],"user":{"authenticated":false,"permissions":{"publish":false}},"run":{"button":{"js":"https://run.pstmn.io/button.js","css":"https://run.pstmn.io/button.css"}},"web":"https://www.getpostman.com/","team":{"logo":"https://res.cloudinary.com/postman/image/upload/t_team_logo_pubdoc/v1/team/52de83af874f462001674ddb9bdc1bfca2ae7a02c72a76650e1d6907bc6253c0","favicon":"https://etisoftware.com/favicon.ico"},"isEnvFetchError":false,"languages":"[{\"key\":\"csharp\",\"label\":\"C#\",\"variant\":\"HttpClient\"},{\"key\":\"csharp\",\"label\":\"C#\",\"variant\":\"RestSharp\"},{\"key\":\"curl\",\"label\":\"cURL\",\"variant\":\"cURL\"},{\"key\":\"dart\",\"label\":\"Dart\",\"variant\":\"http\"},{\"key\":\"go\",\"label\":\"Go\",\"variant\":\"Native\"},{\"key\":\"http\",\"label\":\"HTTP\",\"variant\":\"HTTP\"},{\"key\":\"java\",\"label\":\"Java\",\"variant\":\"OkHttp\"},{\"key\":\"java\",\"label\":\"Java\",\"variant\":\"Unirest\"},{\"key\":\"javascript\",\"label\":\"JavaScript\",\"variant\":\"Fetch\"},{\"key\":\"javascript\",\"label\":\"JavaScript\",\"variant\":\"jQuery\"},{\"key\":\"javascript\",\"label\":\"JavaScript\",\"variant\":\"XHR\"},{\"key\":\"c\",\"label\":\"C\",\"variant\":\"libcurl\"},{\"key\":\"nodejs\",\"label\":\"NodeJs\",\"variant\":\"Axios\"},{\"key\":\"nodejs\",\"label\":\"NodeJs\",\"variant\":\"Native\"},{\"key\":\"nodejs\",\"label\":\"NodeJs\",\"variant\":\"Request\"},{\"key\":\"nodejs\",\"label\":\"NodeJs\",\"variant\":\"Unirest\"},{\"key\":\"objective-c\",\"label\":\"Objective-C\",\"variant\":\"NSURLSession\"},{\"key\":\"ocaml\",\"label\":\"OCaml\",\"variant\":\"Cohttp\"},{\"key\":\"php\",\"label\":\"PHP\",\"variant\":\"cURL\"},{\"key\":\"php\",\"label\":\"PHP\",\"variant\":\"Guzzle\"},{\"key\":\"php\",\"label\":\"PHP\",\"variant\":\"HTTP_Request2\"},{\"key\":\"php\",\"label\":\"PHP\",\"variant\":\"pecl_http\"},{\"key\":\"powershell\",\"label\":\"PowerShell\",\"variant\":\"RestMethod\"},{\"key\":\"python\",\"label\":\"Python\",\"variant\":\"http.client\"},{\"key\":\"python\",\"label\":\"Python\",\"variant\":\"Requests\"},{\"key\":\"r\",\"label\":\"R\",\"variant\":\"httr\"},{\"key\":\"r\",\"label\":\"R\",\"variant\":\"RCurl\"},{\"key\":\"ruby\",\"label\":\"Ruby\",\"variant\":\"Net::HTTP\"},{\"key\":\"shell\",\"label\":\"Shell\",\"variant\":\"Httpie\"},{\"key\":\"shell\",\"label\":\"Shell\",\"variant\":\"wget\"},{\"key\":\"swift\",\"label\":\"Swift\",\"variant\":\"URLSession\"}]","languageSettings":[{"key":"csharp","label":"C#","variant":"HttpClient"},{"key":"csharp","label":"C#","variant":"RestSharp"},{"key":"curl","label":"cURL","variant":"cURL"},{"key":"dart","label":"Dart","variant":"http"},{"key":"go","label":"Go","variant":"Native"},{"key":"http","label":"HTTP","variant":"HTTP"},{"key":"java","label":"Java","variant":"OkHttp"},{"key":"java","label":"Java","variant":"Unirest"},{"key":"javascript","label":"JavaScript","variant":"Fetch"},{"key":"javascript","label":"JavaScript","variant":"jQuery"},{"key":"javascript","label":"JavaScript","variant":"XHR"},{"key":"c","label":"C","variant":"libcurl"},{"key":"nodejs","label":"NodeJs","variant":"Axios"},{"key":"nodejs","label":"NodeJs","variant":"Native"},{"key":"nodejs","label":"NodeJs","variant":"Request"},{"key":"nodejs","label":"NodeJs","variant":"Unirest"},{"key":"objective-c","label":"Objective-C","variant":"NSURLSession"},{"key":"ocaml","label":"OCaml","variant":"Cohttp"},{"key":"php","label":"PHP","variant":"cURL"},{"key":"php","label":"PHP","variant":"Guzzle"},{"key":"php","label":"PHP","variant":"HTTP_Request2"},{"key":"php","label":"PHP","variant":"pecl_http"},{"key":"powershell","label":"PowerShell","variant":"RestMethod"},{"key":"python","label":"Python","variant":"http.client"},{"key":"python","label":"Python","variant":"Requests"},{"key":"r","label":"R","variant":"httr"},{"key":"r","label":"R","variant":"RCurl"},{"key":"ruby","label":"Ruby","variant":"Net::HTTP"},{"key":"shell","label":"Shell","variant":"Httpie"},{"key":"shell","label":"Shell","variant":"wget"},{"key":"swift","label":"Swift","variant":"URLSession"}],"languageOptions":[{"label":"C# - HttpClient","value":"csharp - HttpClient - C#"},{"label":"C# - RestSharp","value":"csharp - RestSharp - C#"},{"label":"cURL - cURL","value":"curl - cURL - cURL"},{"label":"Dart - http","value":"dart - http - Dart"},{"label":"Go - Native","value":"go - Native - Go"},{"label":"HTTP - HTTP","value":"http - HTTP - HTTP"},{"label":"Java - OkHttp","value":"java - OkHttp - Java"},{"label":"Java - Unirest","value":"java - Unirest - Java"},{"label":"JavaScript - Fetch","value":"javascript - Fetch - JavaScript"},{"label":"JavaScript - jQuery","value":"javascript - jQuery - JavaScript"},{"label":"JavaScript - XHR","value":"javascript - XHR - JavaScript"},{"label":"C - libcurl","value":"c - libcurl - C"},{"label":"NodeJs - Axios","value":"nodejs - Axios - NodeJs"},{"label":"NodeJs - Native","value":"nodejs - Native - NodeJs"},{"label":"NodeJs - Request","value":"nodejs - Request - NodeJs"},{"label":"NodeJs - Unirest","value":"nodejs - Unirest - NodeJs"},{"label":"Objective-C - NSURLSession","value":"objective-c - NSURLSession - Objective-C"},{"label":"OCaml - Cohttp","value":"ocaml - Cohttp - OCaml"},{"label":"PHP - cURL","value":"php - cURL - PHP"},{"label":"PHP - Guzzle","value":"php - Guzzle - PHP"},{"label":"PHP - HTTP_Request2","value":"php - HTTP_Request2 - PHP"},{"label":"PHP - pecl_http","value":"php - pecl_http - PHP"},{"label":"PowerShell - RestMethod","value":"powershell - RestMethod - PowerShell"},{"label":"Python - http.client","value":"python - http.client - Python"},{"label":"Python - Requests","value":"python - Requests - Python"},{"label":"R - httr","value":"r - httr - R"},{"label":"R - RCurl","value":"r - RCurl - R"},{"label":"Ruby - Net::HTTP","value":"ruby - Net::HTTP - Ruby"},{"label":"Shell - Httpie","value":"shell - Httpie - Shell"},{"label":"Shell - wget","value":"shell - wget - Shell"},{"label":"Swift - URLSession","value":"swift - URLSession - Swift"}],"layoutOptions":[{"value":"classic-single-column","label":"Single Column"},{"value":"classic-double-column","label":"Double Column"}],"versionOptions":[],"environmentOptions":[{"value":"0","label":"No Environment"},{"label":"ETI-Environment","value":"11528456-ccd84c46-e859-40f6-be77-828ec604591a"}],"canonicalUrl":"https://xos.etisoftware.com/view/metadata/2s93z9cNYg"}