Browse code

Add Service hierarchy to rest api

Signed-off-by: Alessandro Boch <aboch@docker.com>

Alessandro Boch authored on 2015/06/10 08:38:11
Showing 4 changed files
... ...
@@ -91,16 +91,25 @@ func (h *httpHandler) initRouter() {
91 91
 			{"/networks/" + nwID + "/endpoints", []string{"partial-id", epPID}, procGetEndpoints},
92 92
 			{"/networks/" + nwID + "/endpoints", nil, procGetEndpoints},
93 93
 			{"/networks/" + nwID + "/endpoints/" + epID, nil, procGetEndpoint},
94
+			{"/services", []string{"network", nwName}, procGetServices},
95
+			{"/services", []string{"name", epName}, procGetServices},
96
+			{"/services", []string{"partial-id", epPID}, procGetServices},
97
+			{"/services", nil, procGetServices},
98
+			{"/services/" + epID, nil, procGetService},
94 99
 		},
95 100
 		"POST": {
96 101
 			{"/networks", nil, procCreateNetwork},
97 102
 			{"/networks/" + nwID + "/endpoints", nil, procCreateEndpoint},
98 103
 			{"/networks/" + nwID + "/endpoints/" + epID + "/containers", nil, procJoinEndpoint},
104
+			{"/services", nil, procPublishService},
105
+			{"/services/" + epID + "/backend", nil, procAttachBackend},
99 106
 		},
100 107
 		"DELETE": {
101 108
 			{"/networks/" + nwID, nil, procDeleteNetwork},
102 109
 			{"/networks/" + nwID + "/endpoints/" + epID, nil, procDeleteEndpoint},
103 110
 			{"/networks/" + nwID + "/endpoints/" + epID + "/containers/" + cnID, nil, procLeaveEndpoint},
111
+			{"/services/" + epID, nil, procUnpublishService},
112
+			{"/services/" + epID + "/backend/" + cnID, nil, procDetachBackend},
104 113
 		},
105 114
 	}
106 115
 
... ...
@@ -355,7 +364,7 @@ func procGetEndpoints(c libnetwork.NetworkController, vars map[string]string, bo
355 355
 			list = append(list, buildEndpointResource(ep))
356 356
 		}
357 357
 	} else if queryByPid {
358
-		// Return all the prefix-matching networks
358
+		// Return all the prefix-matching endpoints
359 359
 		l := func(ep libnetwork.Endpoint) bool {
360 360
 			if strings.HasPrefix(ep.ID(), shortID) {
361 361
 				list = append(list, buildEndpointResource(ep))
... ...
@@ -448,6 +457,153 @@ func procDeleteEndpoint(c libnetwork.NetworkController, vars map[string]string,
448 448
 	return nil, &successResponse
449 449
 }
450 450
 
451
+/******************
452
+ Service interface
453
+*******************/
454
+func procGetServices(c libnetwork.NetworkController, vars map[string]string, body []byte) (interface{}, *responseStatus) {
455
+	// Look for query filters and validate
456
+	nwName, filterByNwName := vars[urlNwName]
457
+	svName, queryBySvName := vars[urlEpName]
458
+	shortID, queryBySvPID := vars[urlEpPID]
459
+
460
+	if filterByNwName && queryBySvName || filterByNwName && queryBySvPID || queryBySvName && queryBySvPID {
461
+		return nil, &badQueryResponse
462
+	}
463
+
464
+	var list []*endpointResource
465
+
466
+	switch {
467
+	case filterByNwName:
468
+		// return all service present on the specified network
469
+		nw, errRsp := findNetwork(c, nwName, byName)
470
+		if !errRsp.isOK() {
471
+			return list, &successResponse
472
+		}
473
+		for _, ep := range nw.Endpoints() {
474
+			epr := buildEndpointResource(ep)
475
+			list = append(list, epr)
476
+		}
477
+	case queryBySvName:
478
+		// Look in each network for the service with the specified name
479
+		l := func(ep libnetwork.Endpoint) bool {
480
+			if ep.Name() == svName {
481
+				list = append(list, buildEndpointResource(ep))
482
+				return true
483
+			}
484
+			return false
485
+		}
486
+		for _, nw := range c.Networks() {
487
+			nw.WalkEndpoints(l)
488
+		}
489
+	case queryBySvPID:
490
+		// Return all the prefix-matching services
491
+		l := func(ep libnetwork.Endpoint) bool {
492
+			if strings.HasPrefix(ep.ID(), shortID) {
493
+				list = append(list, buildEndpointResource(ep))
494
+			}
495
+			return false
496
+		}
497
+		for _, nw := range c.Networks() {
498
+			nw.WalkEndpoints(l)
499
+		}
500
+	default:
501
+		for _, nw := range c.Networks() {
502
+			for _, ep := range nw.Endpoints() {
503
+				epr := buildEndpointResource(ep)
504
+				list = append(list, epr)
505
+			}
506
+		}
507
+	}
508
+
509
+	return list, &successResponse
510
+}
511
+
512
+func procGetService(c libnetwork.NetworkController, vars map[string]string, body []byte) (interface{}, *responseStatus) {
513
+	epT, epBy := detectEndpointTarget(vars)
514
+	sv, errRsp := findService(c, epT, epBy)
515
+	if !errRsp.isOK() {
516
+		return nil, endpointToService(errRsp)
517
+	}
518
+	return buildEndpointResource(sv), &successResponse
519
+}
520
+
521
+func procPublishService(c libnetwork.NetworkController, vars map[string]string, body []byte) (interface{}, *responseStatus) {
522
+	var sp servicePublish
523
+
524
+	err := json.Unmarshal(body, &sp)
525
+	if err != nil {
526
+		return "", &responseStatus{Status: "Invalid body: " + err.Error(), StatusCode: http.StatusBadRequest}
527
+	}
528
+
529
+	n, errRsp := findNetwork(c, sp.Network, byName)
530
+	if !errRsp.isOK() {
531
+		return "", errRsp
532
+	}
533
+
534
+	var setFctList []libnetwork.EndpointOption
535
+	if sp.ExposedPorts != nil {
536
+		setFctList = append(setFctList, libnetwork.CreateOptionExposedPorts(sp.ExposedPorts))
537
+	}
538
+	if sp.PortMapping != nil {
539
+		setFctList = append(setFctList, libnetwork.CreateOptionPortMapping(sp.PortMapping))
540
+	}
541
+
542
+	ep, err := n.CreateEndpoint(sp.Name, setFctList...)
543
+	if err != nil {
544
+		return "", endpointToService(convertNetworkError(err))
545
+	}
546
+
547
+	return ep.ID(), &createdResponse
548
+}
549
+
550
+func procUnpublishService(c libnetwork.NetworkController, vars map[string]string, body []byte) (interface{}, *responseStatus) {
551
+	epT, epBy := detectEndpointTarget(vars)
552
+	sv, errRsp := findService(c, epT, epBy)
553
+	if !errRsp.isOK() {
554
+		return nil, errRsp
555
+	}
556
+	err := sv.Delete()
557
+	if err != nil {
558
+		return nil, endpointToService(convertNetworkError(err))
559
+	}
560
+	return nil, &successResponse
561
+}
562
+
563
+func procAttachBackend(c libnetwork.NetworkController, vars map[string]string, body []byte) (interface{}, *responseStatus) {
564
+	var bk endpointJoin
565
+	err := json.Unmarshal(body, &bk)
566
+	if err != nil {
567
+		return nil, &responseStatus{Status: "Invalid body: " + err.Error(), StatusCode: http.StatusBadRequest}
568
+	}
569
+
570
+	epT, epBy := detectEndpointTarget(vars)
571
+	sv, errRsp := findService(c, epT, epBy)
572
+	if !errRsp.isOK() {
573
+		return nil, errRsp
574
+	}
575
+
576
+	err = sv.Join(bk.ContainerID, bk.parseOptions()...)
577
+	if err != nil {
578
+		return nil, convertNetworkError(err)
579
+	}
580
+	return sv.Info().SandboxKey(), &successResponse
581
+}
582
+
583
+func procDetachBackend(c libnetwork.NetworkController, vars map[string]string, body []byte) (interface{}, *responseStatus) {
584
+	epT, epBy := detectEndpointTarget(vars)
585
+	sv, errRsp := findService(c, epT, epBy)
586
+	if !errRsp.isOK() {
587
+		return nil, errRsp
588
+	}
589
+
590
+	err := sv.Leave(vars[urlCnID])
591
+	if err != nil {
592
+		return nil, convertNetworkError(err)
593
+	}
594
+
595
+	return nil, &successResponse
596
+}
597
+
451 598
 /***********
452 599
   Utilities
453 600
 ************/
... ...
@@ -492,7 +648,7 @@ func findNetwork(c libnetwork.NetworkController, s string, by int) (libnetwork.N
492 492
 		panic(fmt.Sprintf("unexpected selector for network search: %d", by))
493 493
 	}
494 494
 	if err != nil {
495
-		if _, ok := err.(libnetwork.ErrNoSuchNetwork); ok {
495
+		if _, ok := err.(types.NotFoundError); ok {
496 496
 			return nil, &responseStatus{Status: "Resource not found: Network", StatusCode: http.StatusNotFound}
497 497
 		}
498 498
 		return nil, &responseStatus{Status: err.Error(), StatusCode: http.StatusBadRequest}
... ...
@@ -518,7 +674,7 @@ func findEndpoint(c libnetwork.NetworkController, ns, es string, nwBy, epBy int)
518 518
 		panic(fmt.Sprintf("unexpected selector for endpoint search: %d", epBy))
519 519
 	}
520 520
 	if err != nil {
521
-		if _, ok := err.(libnetwork.ErrNoSuchEndpoint); ok {
521
+		if _, ok := err.(types.NotFoundError); ok {
522 522
 			return nil, &responseStatus{Status: "Resource not found: Endpoint", StatusCode: http.StatusNotFound}
523 523
 		}
524 524
 		return nil, &responseStatus{Status: err.Error(), StatusCode: http.StatusBadRequest}
... ...
@@ -526,6 +682,34 @@ func findEndpoint(c libnetwork.NetworkController, ns, es string, nwBy, epBy int)
526 526
 	return ep, &successResponse
527 527
 }
528 528
 
529
+func findService(c libnetwork.NetworkController, svs string, svBy int) (libnetwork.Endpoint, *responseStatus) {
530
+	for _, nw := range c.Networks() {
531
+		var (
532
+			ep  libnetwork.Endpoint
533
+			err error
534
+		)
535
+		switch svBy {
536
+		case byID:
537
+			ep, err = nw.EndpointByID(svs)
538
+		case byName:
539
+			ep, err = nw.EndpointByName(svs)
540
+		default:
541
+			panic(fmt.Sprintf("unexpected selector for service search: %d", svBy))
542
+		}
543
+		if err == nil {
544
+			return ep, &successResponse
545
+		} else if _, ok := err.(types.NotFoundError); !ok {
546
+			return nil, convertNetworkError(err)
547
+		}
548
+	}
549
+	return nil, &responseStatus{Status: "Service not found", StatusCode: http.StatusNotFound}
550
+}
551
+
552
+func endpointToService(rsp *responseStatus) *responseStatus {
553
+	rsp.Status = strings.Replace(rsp.Status, "endpoint", "service", -1)
554
+	return rsp
555
+}
556
+
529 557
 func convertNetworkError(err error) *responseStatus {
530 558
 	var code int
531 559
 	switch err.(type) {
... ...
@@ -81,7 +81,14 @@ func createTestNetwork(t *testing.T, network string) (libnetwork.NetworkControll
81 81
 		t.Fatal(err)
82 82
 	}
83 83
 
84
-	nw, err := c.NewNetwork(bridgeNetType, network, nil)
84
+	netOption := options.Generic{
85
+		netlabel.GenericData: options.Generic{
86
+			"BridgeName":            network,
87
+			"AllowNonDefaultBridge": true,
88
+		},
89
+	}
90
+	netGeneric := libnetwork.NetworkOptionGeneric(netOption)
91
+	nw, err := c.NewNetwork(bridgeNetType, network, netGeneric)
85 92
 	if err != nil {
86 93
 		t.Fatal(err)
87 94
 	}
... ...
@@ -507,6 +514,447 @@ func TestGetNetworksAndEndpoints(t *testing.T) {
507 507
 	}
508 508
 }
509 509
 
510
+func TestProcGetServices(t *testing.T) {
511
+	defer netutils.SetupTestNetNS(t)()
512
+
513
+	c, err := libnetwork.New("")
514
+	if err != nil {
515
+		t.Fatal(err)
516
+	}
517
+
518
+	err = c.ConfigureNetworkDriver(bridgeNetType, nil)
519
+	if err != nil {
520
+		t.Fatal(err)
521
+	}
522
+
523
+	// Create 2 networks
524
+	netName1 := "production"
525
+	netOption := options.Generic{
526
+		netlabel.GenericData: options.Generic{
527
+			"BridgeName":            netName1,
528
+			"AllowNonDefaultBridge": true,
529
+		},
530
+	}
531
+	nw1, err := c.NewNetwork(bridgeNetType, netName1, libnetwork.NetworkOptionGeneric(netOption))
532
+	if err != nil {
533
+		t.Fatal(err)
534
+	}
535
+
536
+	netName2 := "work-dev"
537
+	netOption = options.Generic{
538
+		netlabel.GenericData: options.Generic{
539
+			"BridgeName":            netName2,
540
+			"AllowNonDefaultBridge": true,
541
+		},
542
+	}
543
+	nw2, err := c.NewNetwork(bridgeNetType, netName2, libnetwork.NetworkOptionGeneric(netOption))
544
+	if err != nil {
545
+		t.Fatal(err)
546
+	}
547
+
548
+	vars := make(map[string]string)
549
+	li, errRsp := procGetServices(c, vars, nil)
550
+	if !errRsp.isOK() {
551
+		t.Fatalf("Unexpected failure: %v", errRsp)
552
+	}
553
+	list := i2eL(li)
554
+	if len(list) != 0 {
555
+		t.Fatalf("Unexpected services in response: %v", list)
556
+	}
557
+
558
+	// Add a couple of services on one network and one on the other network
559
+	ep11, err := nw1.CreateEndpoint("db-prod")
560
+	if err != nil {
561
+		t.Fatal(err)
562
+	}
563
+	ep12, err := nw1.CreateEndpoint("web-prod")
564
+	if err != nil {
565
+		t.Fatal(err)
566
+	}
567
+	ep21, err := nw2.CreateEndpoint("db-dev")
568
+	if err != nil {
569
+		t.Fatal(err)
570
+	}
571
+
572
+	li, errRsp = procGetServices(c, vars, nil)
573
+	if !errRsp.isOK() {
574
+		t.Fatalf("Unexpected failure: %v", errRsp)
575
+	}
576
+	list = i2eL(li)
577
+	if len(list) != 3 {
578
+		t.Fatalf("Unexpected services in response: %v", list)
579
+	}
580
+
581
+	// Filter by network
582
+	vars[urlNwName] = netName1
583
+	li, errRsp = procGetServices(c, vars, nil)
584
+	if !errRsp.isOK() {
585
+		t.Fatalf("Unexpected failure: %v", errRsp)
586
+	}
587
+	list = i2eL(li)
588
+	if len(list) != 2 {
589
+		t.Fatalf("Unexpected services in response: %v", list)
590
+	}
591
+
592
+	vars[urlNwName] = netName2
593
+	li, errRsp = procGetServices(c, vars, nil)
594
+	if !errRsp.isOK() {
595
+		t.Fatalf("Unexpected failure: %v", errRsp)
596
+	}
597
+	list = i2eL(li)
598
+	if len(list) != 1 {
599
+		t.Fatalf("Unexpected services in response: %v", list)
600
+	}
601
+
602
+	vars[urlNwName] = "unknown-network"
603
+	li, errRsp = procGetServices(c, vars, nil)
604
+	if !errRsp.isOK() {
605
+		t.Fatalf("Unexpected failure: %v", errRsp)
606
+	}
607
+	list = i2eL(li)
608
+	if len(list) != 0 {
609
+		t.Fatalf("Unexpected services in response: %v", list)
610
+	}
611
+
612
+	// Query by name
613
+	delete(vars, urlNwName)
614
+	vars[urlEpName] = "db-prod"
615
+	li, errRsp = procGetServices(c, vars, nil)
616
+	if !errRsp.isOK() {
617
+		t.Fatalf("Unexpected failure: %v", errRsp)
618
+	}
619
+	list = i2eL(li)
620
+	if len(list) != 1 {
621
+		t.Fatalf("Unexpected services in response: %v", list)
622
+	}
623
+
624
+	vars[urlEpName] = "no-service"
625
+	li, errRsp = procGetServices(c, vars, nil)
626
+	if !errRsp.isOK() {
627
+		t.Fatalf("Unexpected failure: %v", errRsp)
628
+	}
629
+	list = i2eL(li)
630
+	if len(list) != 0 {
631
+		t.Fatalf("Unexpected services in response: %v", list)
632
+	}
633
+
634
+	// Query by id or partial id
635
+	delete(vars, urlEpName)
636
+	vars[urlEpPID] = ep12.ID()
637
+	li, errRsp = procGetServices(c, vars, nil)
638
+	if !errRsp.isOK() {
639
+		t.Fatalf("Unexpected failure: %v", errRsp)
640
+	}
641
+	list = i2eL(li)
642
+	if len(list) != 1 {
643
+		t.Fatalf("Unexpected services in response: %v", list)
644
+	}
645
+	if list[0].ID != ep12.ID() {
646
+		t.Fatalf("Unexpected element in response: %v", list)
647
+	}
648
+
649
+	vars[urlEpPID] = "non-id"
650
+	li, errRsp = procGetServices(c, vars, nil)
651
+	if !errRsp.isOK() {
652
+		t.Fatalf("Unexpected failure: %v", errRsp)
653
+	}
654
+	list = i2eL(li)
655
+	if len(list) != 0 {
656
+		t.Fatalf("Unexpected services in response: %v", list)
657
+	}
658
+
659
+	delete(vars, urlEpPID)
660
+	err = ep11.Delete()
661
+	if err != nil {
662
+		t.Fatal(err)
663
+	}
664
+	err = ep12.Delete()
665
+	if err != nil {
666
+		t.Fatal(err)
667
+	}
668
+	err = ep21.Delete()
669
+	if err != nil {
670
+		t.Fatal(err)
671
+	}
672
+
673
+	li, errRsp = procGetServices(c, vars, nil)
674
+	if !errRsp.isOK() {
675
+		t.Fatalf("Unexpected failure: %v", errRsp)
676
+	}
677
+	list = i2eL(li)
678
+	if len(list) != 0 {
679
+		t.Fatalf("Unexpected services in response: %v", list)
680
+	}
681
+}
682
+
683
+func TestProcGetService(t *testing.T) {
684
+	defer netutils.SetupTestNetNS(t)()
685
+
686
+	c, nw := createTestNetwork(t, "network")
687
+	ep1, err := nw.CreateEndpoint("db")
688
+	if err != nil {
689
+		t.Fatal(err)
690
+	}
691
+	ep2, err := nw.CreateEndpoint("web")
692
+	if err != nil {
693
+		t.Fatal(err)
694
+	}
695
+
696
+	vars := map[string]string{urlEpID: ""}
697
+	_, errRsp := procGetService(c, vars, nil)
698
+	if errRsp.isOK() {
699
+		t.Fatalf("Expected failure, but suceeded")
700
+	}
701
+	if errRsp.StatusCode != http.StatusBadRequest {
702
+		t.Fatalf("Expected %d, but got: %d", http.StatusBadRequest, errRsp.StatusCode)
703
+	}
704
+
705
+	vars[urlEpID] = "unknown-service-id"
706
+	_, errRsp = procGetService(c, vars, nil)
707
+	if errRsp.isOK() {
708
+		t.Fatalf("Expected failure, but suceeded")
709
+	}
710
+	if errRsp.StatusCode != http.StatusNotFound {
711
+		t.Fatalf("Expected %d, but got: %d. (%v)", http.StatusNotFound, errRsp.StatusCode, errRsp)
712
+	}
713
+
714
+	vars[urlEpID] = ep1.ID()
715
+	si, errRsp := procGetService(c, vars, nil)
716
+	if !errRsp.isOK() {
717
+		t.Fatalf("Unexpected failure: %v", errRsp)
718
+	}
719
+	sv := i2e(si)
720
+	if sv.ID != ep1.ID() {
721
+		t.Fatalf("Unexpected service resource returned: %v", sv)
722
+	}
723
+
724
+	vars[urlEpID] = ep2.ID()
725
+	si, errRsp = procGetService(c, vars, nil)
726
+	if !errRsp.isOK() {
727
+		t.Fatalf("Unexpected failure: %v", errRsp)
728
+	}
729
+	sv = i2e(si)
730
+	if sv.ID != ep2.ID() {
731
+		t.Fatalf("Unexpected service resource returned: %v", sv)
732
+	}
733
+}
734
+
735
+func TestProcPublishUnpublishService(t *testing.T) {
736
+	defer netutils.SetupTestNetNS(t)()
737
+
738
+	c, _ := createTestNetwork(t, "network")
739
+	vars := make(map[string]string)
740
+
741
+	vbad, err := json.Marshal("bad service create data")
742
+	if err != nil {
743
+		t.Fatal(err)
744
+	}
745
+	_, errRsp := procPublishService(c, vars, vbad)
746
+	if errRsp == &createdResponse {
747
+		t.Fatalf("Expected to fail but succeeded")
748
+	}
749
+	if errRsp.StatusCode != http.StatusBadRequest {
750
+		t.Fatalf("Expected %d. Got: %v", http.StatusBadRequest, errRsp)
751
+	}
752
+
753
+	b, err := json.Marshal(servicePublish{Name: ""})
754
+	if err != nil {
755
+		t.Fatal(err)
756
+	}
757
+	_, errRsp = procPublishService(c, vars, b)
758
+	if errRsp == &createdResponse {
759
+		t.Fatalf("Expected to fail but succeeded")
760
+	}
761
+	if errRsp.StatusCode != http.StatusBadRequest {
762
+		t.Fatalf("Expected %d. Got: %v", http.StatusBadRequest, errRsp)
763
+	}
764
+
765
+	b, err = json.Marshal(servicePublish{Name: "db"})
766
+	if err != nil {
767
+		t.Fatal(err)
768
+	}
769
+	_, errRsp = procPublishService(c, vars, b)
770
+	if errRsp == &createdResponse {
771
+		t.Fatalf("Expected to fail but succeeded")
772
+	}
773
+	if errRsp.StatusCode != http.StatusBadRequest {
774
+		t.Fatalf("Expected %d. Got: %v", http.StatusBadRequest, errRsp)
775
+	}
776
+
777
+	b, err = json.Marshal(servicePublish{Name: "db", Network: "unknown-network"})
778
+	if err != nil {
779
+		t.Fatal(err)
780
+	}
781
+	_, errRsp = procPublishService(c, vars, b)
782
+	if errRsp == &createdResponse {
783
+		t.Fatalf("Expected to fail but succeeded")
784
+	}
785
+	if errRsp.StatusCode != http.StatusNotFound {
786
+		t.Fatalf("Expected %d. Got: %v", http.StatusNotFound, errRsp)
787
+	}
788
+
789
+	b, err = json.Marshal(servicePublish{Name: "", Network: "network"})
790
+	if err != nil {
791
+		t.Fatal(err)
792
+	}
793
+	_, errRsp = procPublishService(c, vars, b)
794
+	if errRsp == &createdResponse {
795
+		t.Fatalf("Expected to fail but succeeded")
796
+	}
797
+	if errRsp.StatusCode != http.StatusBadRequest {
798
+		t.Fatalf("Expected %d. Got: %v", http.StatusBadRequest, errRsp)
799
+	}
800
+
801
+	b, err = json.Marshal(servicePublish{Name: "db", Network: "network"})
802
+	if err != nil {
803
+		t.Fatal(err)
804
+	}
805
+	_, errRsp = procPublishService(c, vars, b)
806
+	if errRsp != &createdResponse {
807
+		t.Fatalf("Unexpected failure: %v", errRsp)
808
+	}
809
+
810
+	sp := servicePublish{
811
+		Name:    "web",
812
+		Network: "network",
813
+		ExposedPorts: []types.TransportPort{
814
+			types.TransportPort{Proto: types.TCP, Port: uint16(6000)},
815
+			types.TransportPort{Proto: types.UDP, Port: uint16(500)},
816
+			types.TransportPort{Proto: types.TCP, Port: uint16(700)},
817
+		},
818
+		PortMapping: []types.PortBinding{
819
+			types.PortBinding{Proto: types.TCP, Port: uint16(1230), HostPort: uint16(37000)},
820
+			types.PortBinding{Proto: types.UDP, Port: uint16(1200), HostPort: uint16(36000)},
821
+			types.PortBinding{Proto: types.TCP, Port: uint16(1120), HostPort: uint16(35000)},
822
+		},
823
+	}
824
+	b, err = json.Marshal(sp)
825
+	if err != nil {
826
+		t.Fatal(err)
827
+	}
828
+	si, errRsp := procPublishService(c, vars, b)
829
+	if errRsp != &createdResponse {
830
+		t.Fatalf("Unexpected failure: %v", errRsp)
831
+	}
832
+	sid := i2s(si)
833
+
834
+	vars[urlEpID] = ""
835
+	_, errRsp = procUnpublishService(c, vars, nil)
836
+	if errRsp.isOK() {
837
+		t.Fatalf("Expected failure but succeeded")
838
+	}
839
+	if errRsp.StatusCode != http.StatusBadRequest {
840
+		t.Fatalf("Expected %d. Got: %v", http.StatusBadRequest, errRsp)
841
+	}
842
+
843
+	vars[urlEpID] = "unknown-service-id"
844
+	_, errRsp = procUnpublishService(c, vars, nil)
845
+	if errRsp.isOK() {
846
+		t.Fatalf("Expected failure but succeeded")
847
+	}
848
+	if errRsp.StatusCode != http.StatusNotFound {
849
+		t.Fatalf("Expected %d. Got: %v", http.StatusNotFound, errRsp)
850
+	}
851
+
852
+	vars[urlEpID] = sid
853
+	_, errRsp = procUnpublishService(c, vars, nil)
854
+	if !errRsp.isOK() {
855
+		t.Fatalf("Unexpected failure: %v", errRsp)
856
+	}
857
+
858
+	_, errRsp = procGetService(c, vars, nil)
859
+	if errRsp.isOK() {
860
+		t.Fatalf("Expected failure, but suceeded")
861
+	}
862
+	if errRsp.StatusCode != http.StatusNotFound {
863
+		t.Fatalf("Expected %d, but got: %d. (%v)", http.StatusNotFound, errRsp.StatusCode, errRsp)
864
+	}
865
+}
866
+
867
+func TestAttachDetachBackend(t *testing.T) {
868
+	defer netutils.SetupTestNetNS(t)()
869
+
870
+	c, nw := createTestNetwork(t, "network")
871
+	ep1, err := nw.CreateEndpoint("db")
872
+	if err != nil {
873
+		t.Fatal(err)
874
+	}
875
+
876
+	vars := make(map[string]string)
877
+
878
+	vbad, err := json.Marshal("bad data")
879
+	if err != nil {
880
+		t.Fatal(err)
881
+	}
882
+	_, errRsp := procAttachBackend(c, vars, vbad)
883
+	if errRsp == &successResponse {
884
+		t.Fatalf("Expected failure, got: %v", errRsp)
885
+	}
886
+
887
+	vars[urlEpName] = "endpoint"
888
+	bad, err := json.Marshal(endpointJoin{})
889
+	if err != nil {
890
+		t.Fatal(err)
891
+	}
892
+	_, errRsp = procAttachBackend(c, vars, bad)
893
+	if errRsp == &successResponse {
894
+		t.Fatalf("Expected failure, got: %v", errRsp)
895
+	}
896
+	if errRsp.StatusCode != http.StatusNotFound {
897
+		t.Fatalf("Expected %d. Got: %v", http.StatusNotFound, errRsp)
898
+	}
899
+
900
+	vars[urlEpName] = "db"
901
+	_, errRsp = procAttachBackend(c, vars, bad)
902
+	if errRsp == &successResponse {
903
+		t.Fatalf("Expected failure, got: %v", errRsp)
904
+	}
905
+	if errRsp.StatusCode != http.StatusBadRequest {
906
+		t.Fatalf("Expected %d. Got: %v", http.StatusBadRequest, errRsp)
907
+	}
908
+
909
+	cid := "abcdefghi"
910
+	jl := endpointJoin{ContainerID: cid}
911
+	jlb, err := json.Marshal(jl)
912
+	if err != nil {
913
+		t.Fatal(err)
914
+	}
915
+
916
+	_, errRsp = procAttachBackend(c, vars, jlb)
917
+	if errRsp != &successResponse {
918
+		t.Fatalf("Unexpected failure, got: %v", errRsp)
919
+	}
920
+
921
+	vars[urlEpName] = "endpoint"
922
+	_, errRsp = procDetachBackend(c, vars, nil)
923
+	if errRsp == &successResponse {
924
+		t.Fatalf("Expected failure, got: %v", errRsp)
925
+	}
926
+	if errRsp.StatusCode != http.StatusNotFound {
927
+		t.Fatalf("Expected %d. Got: %v", http.StatusNotFound, errRsp)
928
+	}
929
+
930
+	vars[urlEpName] = "db"
931
+	_, errRsp = procDetachBackend(c, vars, nil)
932
+	if errRsp == &successResponse {
933
+		t.Fatalf("Expected failure, got: %v", errRsp)
934
+	}
935
+	if errRsp.StatusCode != http.StatusBadRequest {
936
+		t.Fatalf("Expected %d. Got: %v", http.StatusBadRequest, errRsp)
937
+	}
938
+
939
+	vars[urlCnID] = cid
940
+	_, errRsp = procDetachBackend(c, vars, nil)
941
+	if errRsp != &successResponse {
942
+		t.Fatalf("Unexpected failure, got: %v", errRsp)
943
+	}
944
+
945
+	err = ep1.Delete()
946
+	if err != nil {
947
+		t.Fatal(err)
948
+	}
949
+}
950
+
510 951
 func TestDetectGetNetworksInvalidQueryComposition(t *testing.T) {
511 952
 	c, err := libnetwork.New("")
512 953
 	if err != nil {
... ...
@@ -532,15 +980,29 @@ func TestDetectGetEndpointsInvalidQueryComposition(t *testing.T) {
532 532
 	}
533 533
 }
534 534
 
535
+func TestDetectGetServicesInvalidQueryComposition(t *testing.T) {
536
+	defer netutils.SetupTestNetNS(t)()
537
+
538
+	c, _ := createTestNetwork(t, "network")
539
+
540
+	vars := map[string]string{urlNwName: "network", urlEpName: "x", urlEpPID: "y"}
541
+	_, errRsp := procGetServices(c, vars, nil)
542
+	if errRsp.StatusCode != http.StatusBadRequest {
543
+		t.Fatalf("Expected %d. Got: %v", http.StatusBadRequest, errRsp)
544
+	}
545
+}
546
+
547
+func TestFindNetworkUtilPanic(t *testing.T) {
548
+	defer checkPanic(t)
549
+	findNetwork(nil, "", -1)
550
+}
551
+
535 552
 func TestFindNetworkUtil(t *testing.T) {
536 553
 	defer netutils.SetupTestNetNS(t)()
537 554
 
538 555
 	c, nw := createTestNetwork(t, "network")
539 556
 	nid := nw.ID()
540 557
 
541
-	defer checkPanic(t)
542
-	findNetwork(c, "", -1)
543
-
544 558
 	_, errRsp := findNetwork(c, "", byName)
545 559
 	if errRsp == &successResponse {
546 560
 		t.Fatalf("Expected to fail but succeeded")
... ...
@@ -577,7 +1039,9 @@ func TestFindNetworkUtil(t *testing.T) {
577 577
 		t.Fatalf("Incorrect libnetwork.Network resource. It has different name: %v", n)
578 578
 	}
579 579
 
580
-	n.Delete()
580
+	if err := n.Delete(); err != nil {
581
+		t.Fatalf("Failed to delete the network: %s", err.Error())
582
+	}
581 583
 
582 584
 	_, errRsp = findNetwork(c, nid, byID)
583 585
 	if errRsp == &successResponse {
... ...
@@ -878,6 +1342,21 @@ func TestJoinLeave(t *testing.T) {
878 878
 	}
879 879
 }
880 880
 
881
+func TestFindEndpointUtilPanic(t *testing.T) {
882
+	defer netutils.SetupTestNetNS(t)()
883
+	defer checkPanic(t)
884
+	c, nw := createTestNetwork(t, "network")
885
+	nid := nw.ID()
886
+	findEndpoint(c, nid, "", byID, -1)
887
+}
888
+
889
+func TestFindServiceUtilPanic(t *testing.T) {
890
+	defer netutils.SetupTestNetNS(t)()
891
+	defer checkPanic(t)
892
+	c, _ := createTestNetwork(t, "network")
893
+	findService(c, "random_service", -1)
894
+}
895
+
881 896
 func TestFindEndpointUtil(t *testing.T) {
882 897
 	defer netutils.SetupTestNetNS(t)()
883 898
 
... ...
@@ -890,9 +1369,6 @@ func TestFindEndpointUtil(t *testing.T) {
890 890
 	}
891 891
 	eid := ep.ID()
892 892
 
893
-	defer checkPanic(t)
894
-	findEndpoint(c, nid, "", byID, -1)
895
-
896 893
 	_, errRsp := findEndpoint(c, nid, "", byID, byName)
897 894
 	if errRsp == &successResponse {
898 895
 		t.Fatalf("Expected failure, but got: %v", errRsp)
... ...
@@ -906,7 +1382,7 @@ func TestFindEndpointUtil(t *testing.T) {
906 906
 		t.Fatalf("Unexepected failure: %v", errRsp)
907 907
 	}
908 908
 
909
-	ep1, errRsp := findEndpoint(c, "second", "secondEp", byName, byName)
909
+	ep1, errRsp := findEndpoint(c, "network", "secondEp", byName, byName)
910 910
 	if errRsp != &successResponse {
911 911
 		t.Fatalf("Unexepected failure: %v", errRsp)
912 912
 	}
... ...
@@ -916,12 +1392,22 @@ func TestFindEndpointUtil(t *testing.T) {
916 916
 		t.Fatalf("Unexepected failure: %v", errRsp)
917 917
 	}
918 918
 
919
-	ep3, errRsp := findEndpoint(c, "second", eid, byName, byID)
919
+	ep3, errRsp := findEndpoint(c, "network", eid, byName, byID)
920
+	if errRsp != &successResponse {
921
+		t.Fatalf("Unexepected failure: %v", errRsp)
922
+	}
923
+
924
+	ep4, errRsp := findService(c, "secondEp", byName)
925
+	if errRsp != &successResponse {
926
+		t.Fatalf("Unexepected failure: %v", errRsp)
927
+	}
928
+
929
+	ep5, errRsp := findService(c, eid, byID)
920 930
 	if errRsp != &successResponse {
921 931
 		t.Fatalf("Unexepected failure: %v", errRsp)
922 932
 	}
923 933
 
924
-	if ep0 != ep1 || ep0 != ep2 || ep0 != ep3 {
934
+	if ep0 != ep1 || ep0 != ep2 || ep0 != ep3 || ep0 != ep4 || ep0 != ep5 {
925 935
 		t.Fatalf("Diffenrent queries returned different endpoints")
926 936
 	}
927 937
 
... ...
@@ -935,7 +1421,7 @@ func TestFindEndpointUtil(t *testing.T) {
935 935
 		t.Fatalf("Expected %d, but got: %d", http.StatusNotFound, errRsp.StatusCode)
936 936
 	}
937 937
 
938
-	_, errRsp = findEndpoint(c, "second", "secondEp", byName, byName)
938
+	_, errRsp = findEndpoint(c, "network", "secondEp", byName, byName)
939 939
 	if errRsp == &successResponse {
940 940
 		t.Fatalf("Expected failure, but got: %v", errRsp)
941 941
 	}
... ...
@@ -951,13 +1437,43 @@ func TestFindEndpointUtil(t *testing.T) {
951 951
 		t.Fatalf("Expected %d, but got: %d", http.StatusNotFound, errRsp.StatusCode)
952 952
 	}
953 953
 
954
-	_, errRsp = findEndpoint(c, "second", eid, byName, byID)
954
+	_, errRsp = findEndpoint(c, "network", eid, byName, byID)
955 955
 	if errRsp == &successResponse {
956 956
 		t.Fatalf("Expected failure, but got: %v", errRsp)
957 957
 	}
958 958
 	if errRsp.StatusCode != http.StatusNotFound {
959 959
 		t.Fatalf("Expected %d, but got: %d", http.StatusNotFound, errRsp.StatusCode)
960 960
 	}
961
+
962
+	_, errRsp = findService(c, "secondEp", byName)
963
+	if errRsp == &successResponse {
964
+		t.Fatalf("Expected failure, but got: %v", errRsp)
965
+	}
966
+	if errRsp.StatusCode != http.StatusNotFound {
967
+		t.Fatalf("Expected %d, but got: %d", http.StatusNotFound, errRsp.StatusCode)
968
+	}
969
+
970
+	_, errRsp = findService(c, eid, byID)
971
+	if errRsp == &successResponse {
972
+		t.Fatalf("Expected failure, but got: %v", errRsp)
973
+	}
974
+	if errRsp.StatusCode != http.StatusNotFound {
975
+		t.Fatalf("Expected %d, but got: %d", http.StatusNotFound, errRsp.StatusCode)
976
+	}
977
+}
978
+
979
+func TestEndpointToService(t *testing.T) {
980
+	r := &responseStatus{Status: "this is one endpoint", StatusCode: http.StatusOK}
981
+	r = endpointToService(r)
982
+	if r.Status != "this is one service" {
983
+		t.Fatalf("endpointToService returned unexpected status string: %s", r.Status)
984
+	}
985
+
986
+	r = &responseStatus{Status: "this is one network", StatusCode: http.StatusOK}
987
+	r = endpointToService(r)
988
+	if r.Status != "this is one network" {
989
+		t.Fatalf("endpointToService returned unexpected status string: %s", r.Status)
990
+	}
961 991
 }
962 992
 
963 993
 func checkPanic(t *testing.T) {
... ...
@@ -52,6 +52,14 @@ type endpointJoin struct {
52 52
 	UseDefaultSandbox bool                   `json:"use_default_sandbox"`
53 53
 }
54 54
 
55
+// servicePublish represents the body of the "publish service" http request message
56
+type servicePublish struct {
57
+	Name         string                `json:"name"`
58
+	Network      string                `json:"network"`
59
+	ExposedPorts []types.TransportPort `json:"exposed_ports"`
60
+	PortMapping  []types.PortBinding   `json:"port_mapping"`
61
+}
62
+
55 63
 // EndpointExtraHost represents the extra host object
56 64
 type endpointExtraHost struct {
57 65
 	Name    string `json:"name"`
... ...
@@ -11,8 +11,8 @@ func (nsn ErrNoSuchNetwork) Error() string {
11 11
 	return fmt.Sprintf("network %s not found", string(nsn))
12 12
 }
13 13
 
14
-// BadRequest denotes the type of this error
15
-func (nsn ErrNoSuchNetwork) BadRequest() {}
14
+// NotFound denotes the type of this error
15
+func (nsn ErrNoSuchNetwork) NotFound() {}
16 16
 
17 17
 // ErrNoSuchEndpoint is returned when a endpoint query finds no result
18 18
 type ErrNoSuchEndpoint string
... ...
@@ -21,8 +21,8 @@ func (nse ErrNoSuchEndpoint) Error() string {
21 21
 	return fmt.Sprintf("endpoint %s not found", string(nse))
22 22
 }
23 23
 
24
-// BadRequest denotes the type of this error
25
-func (nse ErrNoSuchEndpoint) BadRequest() {}
24
+// NotFound denotes the type of this error
25
+func (nse ErrNoSuchEndpoint) NotFound() {}
26 26
 
27 27
 // ErrInvalidNetworkDriver is returned if an invalid driver
28 28
 // name is passed.