=head1 NAME

Brocade::BSC - Configure and query the Brocade SDN controller.

=head1 VERSION

Version 1.0.3

=head1 DESCRIPTION

A I<Brocade::BSC> object is used to model, query, and configure Brocade's
OpenDaylight-based Software-Defined Networking controller.

Most API methods return a duple: a I<Brocade::BSC::Status> object, and a
reference to a data structure with the requested information.  Status
should always be verified before attempting to dereference the second
return value.

=cut

use version; our $VERSION = qv("v1.0.3");

use strict;
use warnings;

package Brocade::BSC;

use Brocade::BSC::Status qw(:constants);

use YAML;
use LWP;
use HTTP::Status qw(:constants :is status_message);
use JSON -convert_blessed_universally;
use XML::Parser;
use Carp::Assert;

=head1 METHODS

=cut

# Constructor ==========================================================
#
=over 4

=item B<new>

Creates a new I<Brocade::BSC> object and populates fields with values
from argument hash, if present, or YAML configuration file.

  ### parameters:
  #   + cfgfile       - path to YAML configuration file specifying controller attributes
  #   + ipAddr        - IP address of controller
  #   + portNum       - TCP port for controller's REST interface
  #   + adminName     - username
  #   + adminPassword - password
  #   + timeout       - for HTTP requests, in seconds
  #
  ### YAML configuration file labels and default values
  #
  #   parameter hash | YAML label  | default value
  #   -------------- | ----------- | -------------
  #   ipAddr         | ctrlIpAddr  | 127.0.0.1
  #   portNum        | ctrlPortNum | 8181
  #   adminName      | ctrlUname   | admin
  #   adminPassword  | ctrlPswd    | admin
  #   timeout        | timeout     | 5

Returns new I<Brocade::BSC> object.

=cut
sub new {
    my $caller = shift;
    my %params = @_;

    my $yamlcfg;
    if ($params{cfgfile} && ( -e $params{cfgfile})) {
        $yamlcfg = YAML::LoadFile($params{cfgfile});
    }
    my $self = {
        ipAddr        => '127.0.0.1',
        portNum       => '8181',
        adminName     => 'admin',
        adminPassword => 'admin',
        timeout       => 5
    };
    if ($yamlcfg) {
        $yamlcfg->{ctrlIpAddr}
            && ($self->{ipAddr} = $yamlcfg->{ctrlIpAddr});
        $yamlcfg->{ctrlPortNum}
            && ($self->{portNum} = $yamlcfg->{ctrlPortNum});
        $yamlcfg->{ctrlUname}
            && ($self->{adminName} = $yamlcfg->{ctrlUname});
        $yamlcfg->{ctrlPswd}
            && ($self->{adminPassword} = $yamlcfg->{ctrlPswd});
        $yamlcfg->{timeout}
            && ($self->{timeout} = $yamlcfg->{timeout});
    }
    map { $params{$_} && ($self->{$_} = $params{$_}) }
        qw(ipAddr portNum adminName adminPassword timeout);
    bless $self;
}    

# Method ===============================================================
# _http_req : semi-private; send HTTP request to BSC Controller
# Parameters: $method (string, req) HTTP verb
#           : $urlpath (string, req) path for REST request
#           : $data (string, opt)
#           : $headerref (hash ref, opt)
# Returns   : HTTP::Response
#
sub _http_req {
    my $self = shift;
    my ($method, $urlpath, $data, $headerref) = @_;
    my %headers = $headerref ? %$headerref : ();

    my $url = "http://$$self{ipAddr}:$$self{portNum}$urlpath";
    my $ua = LWP::UserAgent->new;
    $ua->timeout($self->{timeout});
    my $req = HTTP::Request->new($method => $url);
    while (my($header, $value) = each %headers) {
        $req->header($header => $value);
    }
    if ($data) {
        $req->content($data);
    }
    $req->authorization_basic($$self{adminName}, $$self{adminPassword});

    return $ua->request($req);
}

# Method ===============================================================
#
=item B<as_json>

  # Returns pretty-printed JSON string representing BSC object.

=cut
sub as_json {
    my $self = shift;
    my $json = new JSON->canonical->allow_blessed->convert_blessed;
    return $json->pretty->encode($self);
}

# Method ===============================================================
#
=item B<get_nodes_operational_list>

  # Returns   : BSC::Status
  #           : reference to an array of node names

=cut
sub get_nodes_operational_list {
    my $self = shift;
    my @nodeNames = ();
    my $status = new Brocade::BSC::Status;
    my $urlpath = "/restconf/operational/opendaylight-inventory:nodes";

    my $resp = $self->_http_req('GET', $urlpath);
    if ($resp->code == HTTP_OK) {
        if ($resp->content =~ /\"nodes\"/) {
            my $nodes = decode_json($resp->content)->{nodes}->{node};
            if (! $nodes) {
                $status->code($BSC_DATA_NOT_FOUND);
            }
            else {
                foreach (@$nodes) {
                    push @nodeNames, $_->{id};
                }
                $status->code($BSC_OK);
            }
        }
        else {
            $status->code($BSC_DATA_NOT_FOUND);
        }
    }
    else {
        $status->http_err($resp);
    }
    return ($status, \@nodeNames);
}

# Method ===============================================================
#
=item B<get_node_info>

  # Parameter : node name (string, required)
  # Returns   : BSC::Status
  #           : array reference containing node info

=cut
sub get_node_info {
    my $self = shift;
    my $node = shift;
    my $node_info = undef;
    my $status = new Brocade::BSC::Status;
    my $urlpath = "/restconf/operational/opendaylight-inventory:nodes/node/$node";

    my $resp = $self->_http_req('GET', $urlpath);
    if ($resp->code == HTTP_OK) {
        if ($resp->content =~ /\"node\"/) {
            $node_info = decode_json($resp->content)->{node};
            $status->code($node_info ? $BSC_OK : $BSC_DATA_NOT_FOUND);
        }
    }
    else {
        $status->http_err($resp);
    }
    return ($status, $node_info);
}

# Method ===============================================================
#
=item B<check_node_config_status>

  # Parameter : node name (string, required)
  # Returns   : BSC::Status - NODE_CONFIGURED or NODE_NOT_FOUND

=cut
sub check_node_config_status {
    my $self = shift;
    my $node = shift;
    my $status = new Brocade::BSC::Status;

    my $urlpath = "/restconf/config/opendaylight-inventory:nodes/node/$node";
    my $resp = $self->_http_req('GET', $urlpath);
    $status->code(($resp->code == HTTP_OK)
        ? $BSC_NODE_CONFIGURED : $BSC_NODE_NOT_FOUND);
    return $status;
}

# Method ===============================================================
#
=item B<check_node_conn_status>

  # Parameter : node name (string, required)
  # Returns   : BSC::Status - NODE_CONNECTED or NODE_DISCONNECTED

=cut
sub check_node_conn_status {
    my $self = shift;
    my $node = shift;
    my $status = new Brocade::BSC::Status;
    ($status, my $nodeStatus) = $self->get_all_nodes_conn_status();
    if ($status->ok) {
        $status->code($BSC_NODE_NOT_FOUND);
        foreach (@$nodeStatus) {
            if ($_->{id} eq $node) {
                $status->code($_->{connected} ? $BSC_NODE_CONNECTED
                                              : $BSC_NODE_DISCONNECTED);
                last;
            }
        }
    }
    return $status;
}

# Method ===============================================================
#
=item B<get_all_nodes_in_config>

  # Returns   : BSC::Status
  #           : array reference - list of node identifiers

=cut
sub get_all_nodes_in_config {
    my $self = shift;
    my @nodeNames = ();
    my $status = new Brocade::BSC::Status;
    my $urlpath = "/restconf/config/opendaylight-inventory:nodes";

    my $resp = $self->_http_req('GET', $urlpath);
    if ($resp->code == HTTP_OK) {
        if ($resp->content =~ /\"nodes\"/) {
            my $nodes = decode_json($resp->content)->{nodes}->{node};
            if (! $nodes) {
                $status->code($BSC_DATA_NOT_FOUND);
            }
            else {
                foreach (@$nodes) {
                    push @nodeNames, $_->{id};
                }
                $status->code($BSC_OK);
            }
        }
        else {
            $status->code($BSC_DATA_NOT_FOUND);
        }
    }
    else {
        $status->http_err($resp);
    }
    return ($status, \@nodeNames);
}

# Method ===============================================================
#
=item B<get_all_nodes_conn_status>

  # Returns   : BSC::Status
  #           : reference to array of hashes:
  #             { id        => nodename,
  #               connected => boolean }

=cut
#
# Openflow devices on the Controller are always prefixed with "openflow:"
# Since Openflow devices initiate communication with the Controller, and
# not vice versa as with NETCONF devices, any Openflow devices in the
# operational inventory are shown as connected.
#
sub get_all_nodes_conn_status {
    my $self = shift;
    my @nodeStatus = ();
    my $status = new Brocade::BSC::Status;
    my $connected = undef;
    my $urlpath = "/restconf/operational/opendaylight-inventory:nodes";

    my $resp = $self->_http_req('GET', $urlpath);
    if ($resp->code == HTTP_OK) {
        if ($resp->content =~ /\"nodes\"/) {
            my $nodes = decode_json($resp->content)->{nodes}->{node};
            if (! $nodes) {
                $status->code($BSC_DATA_NOT_FOUND);
            }
            else {
                foreach (@$nodes) {
                    if ($_->{id} =~ /^openflow:/) {
                        $connected = 1;
                    }
                    else {
                        $connected = $_->{"netconf-node-inventory:connected"};
                    }
                    push @nodeStatus, {'id' => $_->{id},
                                       'connected' => $connected}
                }
                $status->code($BSC_OK);
            }
        }
        else {
            $status->code($BSC_DATA_NOT_FOUND);
        }
    }
    else {
        $status->http_err($resp);
    }
    return ($status, \@nodeStatus);
}

# Method ===============================================================
#
=item B<get_netconf_nodes_in_config>

  # Returns   : BSC::Status
  #           : array reference - list of node identifiers

=cut
sub get_netconf_nodes_in_config {
    my $self = shift;
    my @netconf_nodes = undef;

    my ($status, $nodelist_ref) = $self->get_all_nodes_in_config();
    $status->ok and @netconf_nodes = grep !/^openflow:/, @$nodelist_ref;
    return ($status, \@netconf_nodes);
}

# Method ===============================================================
#
=item B<get_all_nodes_conn_status>

  # Returns   : BSC::Status
  #           : reference to array of hashes:
  #             { id        => nodename,
  #               connected => boolean }

=cut
sub get_netconf_nodes_conn_status {
    my $self = shift;
    my @netconf_nodes = undef;

    my ($status, $nodestatus_ref) = $self->get_all_nodes_conn_status();
    $status->ok and
        @netconf_nodes = grep { $_->{id} !~ /^openflow:/ } @$nodestatus_ref;
    return ($status, \@netconf_nodes);
}

# Method ===============================================================
#
=item B<get_schemas>

  # Parameters: node name (string, required)
  # Returns   : BSC::Status
  #           : array reference - supported schemas on node

=cut
sub get_schemas {
    my $self = shift;
    my $node = shift;
    my $schemas = undef;
    my $status = new Brocade::BSC::Status;
    my $urlpath = "/restconf/operational/opendaylight-inventory:nodes/node/"
        . "$node/yang-ext:mount/ietf-netconf-monitoring:netconf-state/schemas";

    my $resp = $self->_http_req('GET', $urlpath);
    if ($resp->code == HTTP_OK) {
        if ($resp->content =~ /\"schemas\"/) {
            $schemas = decode_json($resp->content)->{schemas}->{schema};
            $status->code($schemas ? $BSC_OK : $BSC_DATA_NOT_FOUND);
        }
        else {
            $status->code($BSC_DATA_NOT_FOUND);
        }
    }
    else {
        $status->http_err($resp);
    }
    return ($status, $schemas);
}

# Method ===============================================================
#
=item B<get_schema>

  # Parameters: node name
  #           : YANG schema ID
  #           : YANG schema version
  # Returns   : BSC::Status
  #           : requested YANG schema as formatted JSON

=cut
sub get_schema {
    my $self = shift;
    my ($node, $schemaId, $schemaVersion) = @_;
    my $status = new Brocade::BSC::Status;
    my $schema = undef;

    my $urlpath = "/restconf/operations/opendaylight-inventory:nodes"
        . "/node/$node/yang-ext:mount/ietf-netconf-monitoring:get-schema";
    my $payload = qq({"input":{"identifier":"$schemaId","version":)
        . qq("$schemaVersion","format":"yang"}});
    my %headers = ('content-type'=>'application/yang.data+json',
                   'accept'=>'text/json, text/html, application/xml, */*');

    my $resp = $self->_http_req('POST', $urlpath, $payload, \%headers);
    if ($resp->code == HTTP_OK) {
        my $xmltree_ref = new XML::Parser(Style => 'Tree')->parse($resp->content);
        assert   ($xmltree_ref->[0]          eq 'get-schema');
        assert   ($xmltree_ref->[1][1]       eq 'output');
        assert   ($xmltree_ref->[1][2][1]    eq 'data');
        assert   ($xmltree_ref->[1][2][2][1] == 0);
        $schema = $xmltree_ref->[1][2][2][2];
        $status->code($BSC_OK);
    }
    else {
        $status->http_err($resp);
    }
    return ($status, $schema);
}

# Method ===============================================================
#
=item B<get_netconf_operations>

  # Parameters: node name
  # Returns   : BSC::Status
  #           : hash reference - operations supported by specified node

=cut
sub get_netconf_operations {
    my $self = shift;
    my $node = shift;
    my $operations = undef;
    my $status = new Brocade::BSC::Status;
    my $urlpath = "/restconf/operations/opendaylight-inventory:nodes/node/$node/yang-ext:mount/";

    my $resp = $self->_http_req('GET', $urlpath);
    if ($resp->code == HTTP_OK) {
        if ($resp->content =~ /\"operations\"/) {
            $operations = decode_json($resp->content)->{operations};
            $status->code($BSC_OK);
        }
        else {
            $status->code($BSC_DATA_NOT_FOUND);
        }
    }
    else {
        $status->http_err($resp);
    }
    return ($status, $operations);
}

# Method ===============================================================
#
=item B<get_all_modules_operational_state>

  # Returns   : BSC::Status
  #           : array reference - hashes of module state

=cut
sub get_all_modules_operational_state {
    my $self = shift;
    my $modules = undef;
    my $status = new Brocade::BSC::Status;
    my $urlpath = "/restconf/operational/opendaylight-inventory:nodes/node/controller-config/yang-ext:mount/config:modules";
    
    my $resp = $self->_http_req('GET', $urlpath);
    if ($resp->code == HTTP_OK) {
        if ($resp->content =~ /\"modules\"/) {
            # BVC returns bad JSON on this REST call.  Sanitize.
            my $json = $resp->content;
            $json =~ s/\\\n//g;
            $modules = decode_json($json)->{modules}->{module};
            $status->code($modules ? $BSC_OK : $BSC_DATA_NOT_FOUND);
        }
        else {
            $status->code($BSC_DATA_NOT_FOUND);
        }
    }
    else {
        $status->http_err($resp);
    }
    return ($status, $modules);
}

# Method ===============================================================
# 
=item B<get_module_operational_state>

  # Parameter : module type
  #           : module name
  # Returns   : BSC::Status
  #           : array reference - hash of module state

=cut
sub get_module_operational_state {
    my $self = shift;
    my ($moduleType, $moduleName) = @_;
    my $module = undef;
    my $status = new Brocade::BSC::Status;
    my $urlpath = "/restconf/operational/opendaylight-inventory:nodes/node/controller-config/yang-ext:mount/config:modules/module/$moduleType/$moduleName";

    my $resp = $self->_http_req('GET', $urlpath);
    if ($resp->code == HTTP_OK ) {
        if ($resp->content =~ /\"module\"/) {
            $module = decode_json($resp->content)->{module};
            $status->code($module ? $BSC_OK : $BSC_DATA_NOT_FOUND);
        }
        else {
            $status->code($BSC_DATA_NOT_FOUND);
        }
    }
    else {
        $status->http_err($resp);
    }
    return ($status, $module);
}

# Method ===============================================================
# 
=item B<get_sessions_info>

  # Parameters: node name
  # Returns   : BSC::Status
  #           : hash reference - session listing on specified node

=cut
sub get_sessions_info {
    my $self = shift;
    my $node = shift;
    my $sessions = undef;
    my $status = new Brocade::BSC::Status;
    my $urlpath = "/restconf/operational/opendaylight-inventory:nodes/node/$node/yang-ext:mount/ietf-netconf-monitoring:netconf-state/sessions";

    my $resp = $self->_http_req('GET', $urlpath);
    if ($resp->code == HTTP_OK) {
        if ($resp->content =~ /\"sessions\"/) {
            $sessions = decode_json($resp->content)->{sessions};
            $status->code($sessions ? $BSC_OK : $BSC_DATA_NOT_FOUND);
        }
        else {
            $status->code($BSC_DATA_NOT_FOUND);
        }
    }
    else {
        $status->http_err($resp);
    }
    return ($status, $sessions);
}

# Method ===============================================================
# 
=item B<get_streams_info>

  # Parameters:
  # Returns   : BSC::Status
  #           : hash reference - streams info

=cut
sub get_streams_info {
    my $self = shift;
    my $streams = undef;
    my $status = new Brocade::BSC::Status;
    my $urlpath = "/restconf/streams";

    my $resp = $self->_http_req('GET', $urlpath);
    if ($resp->code == HTTP_OK) {
        if ($resp->content =~ /\"streams\"/) {
            $streams = decode_json($resp->content)->{streams};
            $status->code($streams ? $BSC_OK : $BSC_DATA_NOT_FOUND);
        }
        else {
            $status->code($BSC_DATA_NOT_FOUND);
        }
    }
    else {
        $status->http_err($resp);
    }
    return ($status, $streams);
}

# Method ===============================================================
# 
=item B<get_service_providers_info>

  # Parameters:
  # Returns   : BSC::Status
  #           : array reference ~ name/provider pairs

=cut
sub get_service_providers_info {
    my $self = shift;
    my $service = undef;
    my $status = new Brocade::BSC::Status;
    my $urlpath = "/restconf/config/opendaylight-inventory:nodes/node/controller-config/yang-ext:mount/config:services";

    my $resp = $self->_http_req('GET', $urlpath);
    if ($resp->code == HTTP_OK) {
        if ($resp->content =~ /\"services\"/) {
            $service = decode_json($resp->content)->{services}->{service};
            $status->code($service ? $BSC_OK : $BSC_DATA_NOT_FOUND);
        }
        else {
            $status->code($BSC_DATA_NOT_FOUND);
        }
    }
    else {
        $status->http_err($resp);
    }
    return ($status, $service);
}

# Method ===============================================================
#
=item B<get_service_provider_info>

  # Parameters: node name
  # Returns   : BSC::Status
  #           : array reference ~ name/provider pairs

=cut
sub get_service_provider_info {
    my $self = shift;
    my $name = shift;
    my $service = undef;
    my $status = new Brocade::BSC::Status;
    my $urlpath = "/restconf/config/opendaylight-inventory:nodes/node"
        . "/controller-config/yang-ext:mount/config:services/service/$name";

    my $resp = $self->_http_req('GET', $urlpath);
    if ($resp->code == HTTP_OK) {
        if ($resp->content =~ /\"service\"/) {
            $service = decode_json($resp->content)->{service};
            $status->code($BSC_OK);
        }
        else {
            $status->code($BSC_DATA_NOT_FOUND);
        }
    }
    else {
        $status->http_err($resp);
    }
    return ($status, $service);
}

# Method ===============================================================
#
=item B<add_netconf_node>

Add a mount point on controller for specified node.

  # Parameters: node name
  # Returns   : BSC::Status

=cut
sub add_netconf_node {
    my $self = shift;
    my $node = shift;
    my $status = new Brocade::BSC::Status($BSC_OK);

    my $urlpath = "/restconf/config/opendaylight-inventory:nodes/node/controller-config/yang-ext:mount/config:modules";
    my %headers = ('content-type' => 'application/xml',
                   'accept' => 'application/xml');
    my $xmlPayload = <<END_XML;
        <module xmlns="urn:opendaylight:params:xml:ns:yang:controller:config">
          <type xmlns:prefix="urn:opendaylight:params:xml:ns:yang:controller:md:sal:connector:netconf">prefix:sal-netconf-connector</type>
          <name>$node->{name}</name>
          <address xmlns="urn:opendaylight:params:xml:ns:yang:controller:md:sal:connector:netconf">$node->{ipAddr}</address>
          <port xmlns="urn:opendaylight:params:xml:ns:yang:controller:md:sal:connector:netconf">$node->{portNum}</port>
          <username xmlns="urn:opendaylight:params:xml:ns:yang:controller:md:sal:connector:netconf">$node->{adminName}</username>
          <password xmlns="urn:opendaylight:params:xml:ns:yang:controller:md:sal:connector:netconf">$node->{adminPassword}</password>
          <tcp-only xmlns="urn:opendaylight:params:xml:ns:yang:controller:md:sal:connector:netconf">$node->{tcpOnly}</tcp-only>
          <event-executor xmlns="urn:opendaylight:params:xml:ns:yang:controller:md:sal:connector:netconf">
            <type xmlns:prefix="urn:opendaylight:params:xml:ns:yang:controller:netty">prefix:netty-event-executor</type>
            <name>global-event-executor</name>
          </event-executor>
          <binding-registry xmlns="urn:opendaylight:params:xml:ns:yang:controller:md:sal:connector:netconf">
            <type xmlns:prefix="urn:opendaylight:params:xml:ns:yang:controller:md:sal:binding">prefix:binding-broker-osgi-registry</type>
            <name>binding-osgi-broker</name>
          </binding-registry>
          <dom-registry xmlns="urn:opendaylight:params:xml:ns:yang:controller:md:sal:connector:netconf">
            <type xmlns:prefix="urn:opendaylight:params:xml:ns:yang:controller:md:sal:dom">prefix:dom-broker-osgi-registry</type>
            <name>dom-broker</name>
          </dom-registry>
          <client-dispatcher xmlns="urn:opendaylight:params:xml:ns:yang:controller:md:sal:connector:netconf">
            <type xmlns:prefix="urn:opendaylight:params:xml:ns:yang:controller:config:netconf">prefix:netconf-client-dispatcher</type>
            <name>global-netconf-dispatcher</name>
          </client-dispatcher>
          <processing-executor xmlns="urn:opendaylight:params:xml:ns:yang:controller:md:sal:connector:netconf">
            <type xmlns:prefix="urn:opendaylight:params:xml:ns:yang:controller:threadpool">prefix:threadpool</type>
            <name>global-netconf-processing-executor</name>
          </processing-executor>
        </module>
END_XML

    my $resp = $self->_http_req('POST', $urlpath, $xmlPayload, \%headers);
    $resp->is_success or $status->http_err($resp);
    return $status;
}

# Method ===============================================================
#
=item B<delete_netconf_node>

  # Parameters: node name
  # Returns   : BSC::Status
  #           :

=cut
sub delete_netconf_node {
    my $self = shift;
    my $node = shift;
    my $status = new Brocade::BSC::Status($BSC_OK);
    my $urlpath = "/restconf/config/opendaylight-inventory:nodes/node"
        . "/controller-config/yang-ext:mount/config:modules/module"
        . "/odl-sal-netconf-connector-cfg:sal-netconf-connector/"
        . $node->{name};

    my $resp = $self->_http_req('DELETE', $urlpath);
    $resp->is_success or $status->http_err($resp);
    return $status;
}

# Method ===============================================================
#
# =item B<modify_netconf_node_in_config>
#
#   # Parameters:
#   # Returns   : BSC::Status
#   #           :
#
# =cut
sub modify_netconf_node_in_config {
    my $self = shift;
    my $node = shift;

    die "XXX";
}

# Method ===============================================================
#
=item B<get_ext_mount_config_urlpath>

  # Parameters: node name
  # Returns   : base restconf URL for configuration of mounted netconf node

=cut
sub get_ext_mount_config_urlpath {
    my $self = shift;
    my $node = shift;

    return "/restconf/config/opendaylight-inventory:nodes/node/"
        . "$node/yang-ext:mount/";
}

# Method ===============================================================
#
=item B<get_ext_mount_operational_urlpath>

  # Parameters: node name
  # Returns   : base restconf URL for operational status of mounted netconf node

=cut
sub get_ext_mount_operational_urlpath {
    my $self = shift;
    my $node = shift;

    return "/restconf/operational/opendaylight-inventory:nodes/node/"
        . "$node/yang-ext:mount/";
}

# Method ===============================================================
# 
=item B<get_node_operational_urlpath>

  # Parameters: node name
  # Returns   : base restconf URL for node, operational status

=cut
sub get_node_operational_urlpath {
    my $self = shift;
    my $node = shift;

    return "/restconf/operational/opendaylight-inventory:nodes/node/$node";
}

# Method ===============================================================
#
=item B<get_node_config_urlpath>

  # Parameters: node name
  # Returns   : base restconf URL for node, configuration

=cut
sub get_node_config_urlpath {
    my $self = shift;
    my $node = shift;

    return "/restconf/config/opendaylight-inventory:nodes/node/$node";
}

# Method ===============================================================
#
=item B<get_openflow_nodes_operational_list>

  # Returns   : BSC::Status
  #           : array reference - node names

=cut
sub get_openflow_nodes_operational_list {
    my $self = shift;
    my $status = new Brocade::BSC::Status;
    my @nodelist = ();

    my $urlpath = "/restconf/operational/opendaylight-inventory:nodes";
    my $resp = $self->_http_req('GET', $urlpath);
    if ($resp->code == HTTP_OK) {
        if ($resp->content =~ /\"nodes\"/) {
            my $nodes = decode_json($resp->content)->{nodes}->{node};
            if (! $nodes) {
                $status->code($BSC_DATA_NOT_FOUND);
            }
            else {
                $status->code($BSC_OK);
                foreach (@$nodes) {
                    $_->{id} =~ /^(openflow:[0-9]*)/ && push @nodelist, $1;
                }
            }
        }
    }
    else {
        $status->http_err($resp);
    }
    return ($status, \@nodelist);
}

# Module ===============================================================
1;

=back

=head1 AUTHOR

C<< <pruiklw at cpan.org> >>

=head1 BUGS

Please report any bugs or feature requests to C<bug-brocade-bsc at rt.cpan.org>,
or through the web interface at
L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Brocade-BSC>.
I will be notified, and then you'll automatically be notified of progress on
your bug as I make changes.


=head1 SUPPORT

You can find documentation for this module with the perldoc command.

    perldoc Brocade::BSC


You can also look for information at:

=over 4

=item * RT: CPAN's request tracker (report bugs here)

L<http://rt.cpan.org/NoAuth/Bugs.html?Dist=Brocade-BSC>

=back


=head1 ACKNOWLEDGEMENTS

Brocade::BSC is entirely based on L<pybvc|https://github.com/BRCDCOMM/pybvc>
created by Sergei Garbuzov.


=head1 LICENCE AND COPYRIGHT

Copyright (c) 2015,  BROCADE COMMUNICATIONS SYSTEMS, INC

All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.

2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.

3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from this
software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
THE POSSIBILITY OF SUCH DAMAGE.
