diff --git a/cmd/stackube-controller/stackube-controller.go b/cmd/stackube-controller/stackube-controller.go
index 26a64fa..c820b72 100644
--- a/cmd/stackube-controller/stackube-controller.go
+++ b/cmd/stackube-controller/stackube-controller.go
@@ -11,12 +11,15 @@ import (
 	"git.openstack.org/openstack/stackube/pkg/auth-controller/tenant"
 	"git.openstack.org/openstack/stackube/pkg/network-controller"
 	"git.openstack.org/openstack/stackube/pkg/openstack"
+	"git.openstack.org/openstack/stackube/pkg/service-controller"
 	"git.openstack.org/openstack/stackube/pkg/util"
 
+	extclientset "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
+	"k8s.io/client-go/kubernetes"
+
 	"github.com/golang/glog"
 	"github.com/spf13/pflag"
 	"golang.org/x/sync/errgroup"
-	"k8s.io/client-go/kubernetes"
 )
 
 var (
@@ -28,26 +31,28 @@ var (
 	userGateway = pflag.String("user-gateway", "10.244.0.1", "user Pod network gateway")
 )
 
-func startControllers(kubeconfig, cloudconfig string) error {
+func startControllers(kubeClient *kubernetes.Clientset,
+	osClient *openstack.Client, kubeExtClient *extclientset.Clientset) error {
 	// Creates a new Tenant controller
-	tc, err := tenant.NewTenantController(kubeconfig, cloudconfig)
+	tenantController, err := tenant.NewTenantController(kubeClient, osClient, kubeExtClient)
 	if err != nil {
 		return err
 	}
 
 	// Creates a new Network controller
-	nc, err := network.NewNetworkController(
-		kubeconfig, cloudconfig)
+	networkController, err := network.NewNetworkController(osClient, kubeExtClient)
 	if err != nil {
 		return err
 	}
 
 	// Creates a new RBAC controller
-	rm, err := rbacmanager.NewRBACController(kubeconfig,
-		tc.GetKubeCRDClient(),
-		*userCIDR,
-		*userGateway,
-	)
+	rbacController, err := rbacmanager.NewRBACController(kubeClient, osClient.CRDClient, *userCIDR, *userGateway)
+	if err != nil {
+		return err
+	}
+
+	// Creates a new service controller
+	serviceController, err := service.NewServiceController(kubeClient, osClient)
 	if err != nil {
 		return err
 	}
@@ -56,11 +61,14 @@ func startControllers(kubeconfig, cloudconfig string) error {
 	wg, ctx := errgroup.WithContext(ctx)
 
 	// start auth controllers in stackube
-	wg.Go(func() error { return tc.Run(ctx.Done()) })
-	wg.Go(func() error { return rm.Run(ctx.Done()) })
+	wg.Go(func() error { return tenantController.Run(ctx.Done()) })
+	wg.Go(func() error { return rbacController.Run(ctx.Done()) })
 
 	// start network controller
-	wg.Go(func() error { return nc.Run(ctx.Done()) })
+	wg.Go(func() error { return networkController.Run(ctx.Done()) })
+
+	// start service controller
+	wg.Go(func() error { return serviceController.Run(ctx.Done()) })
 
 	term := make(chan os.Signal)
 	signal.Notify(term, os.Interrupt, syscall.SIGTERM)
@@ -80,23 +88,28 @@ func startControllers(kubeconfig, cloudconfig string) error {
 	return nil
 }
 
-func verifyClientSetting() error {
+func initClients() (*kubernetes.Clientset, *openstack.Client, *extclientset.Clientset, error) {
+	// Create kubernetes client config. Use kubeconfig if given, otherwise assume in-cluster.
 	config, err := util.NewClusterConfig(*kubeconfig)
 	if err != nil {
-		return fmt.Errorf("Init kubernetes cluster failed: %v", err)
+		return nil, nil, nil, fmt.Errorf("failed to build kubeconfig: %v", err)
 	}
-
-	_, err = kubernetes.NewForConfig(config)
+	kubeClient, err := kubernetes.NewForConfig(config)
 	if err != nil {
-		return fmt.Errorf("Init kubernetes clientset failed: %v", err)
+		return nil, nil, nil, fmt.Errorf("failed to create kubernetes clientset: %v", err)
 	}
-
-	_, err = openstack.NewClient(*cloudconfig, *kubeconfig)
+	kubeExtClient, err := extclientset.NewForConfig(config)
 	if err != nil {
-		return fmt.Errorf("Init openstack client failed: %v", err)
+		return nil, nil, nil, fmt.Errorf("failed to create kubernetes apiextensions clientset: %v", err)
 	}
 
-	return nil
+	// Create OpenStack client from config file.
+	osClient, err := openstack.NewClient(*cloudconfig, *kubeconfig)
+	if err != nil {
+		return nil, nil, nil, fmt.Errorf("could't initialize openstack client: %v", err)
+	}
+
+	return kubeClient, osClient, kubeExtClient, nil
 }
 
 func main() {
@@ -104,14 +117,14 @@ func main() {
 	util.InitLogs()
 	defer util.FlushLogs()
 
-	// Verify client setting at the beginning and fail early if there are errors.
-	err := verifyClientSetting()
+	// Initilize kubernetes and openstack clients.
+	kubeClient, osClient, kubeExtClient, err := initClients()
 	if err != nil {
 		glog.Fatal(err)
 	}
 
 	// Start stackube controllers.
-	if err := startControllers(*kubeconfig, *cloudconfig); err != nil {
+	if err := startControllers(kubeClient, osClient, kubeExtClient); err != nil {
 		glog.Fatal(err)
 	}
 }
diff --git a/pkg/auth-controller/rbacmanager/rbac_controller.go b/pkg/auth-controller/rbacmanager/rbac_controller.go
index a348fe8..1ce8d09 100644
--- a/pkg/auth-controller/rbacmanager/rbac_controller.go
+++ b/pkg/auth-controller/rbacmanager/rbac_controller.go
@@ -34,22 +34,10 @@ type Controller struct {
 }
 
 // New creates a new RBAC controller.
-func NewRBACController(kubeconfig string,
-	kubeCRDClient *crdClient.CRDClient,
-	userCIDR string,
-	userGateway string,
-) (*Controller, error) {
-	cfg, err := util.NewClusterConfig(kubeconfig)
-	if err != nil {
-		return nil, fmt.Errorf("init cluster config failed: %v", err)
-	}
-	client, err := kubernetes.NewForConfig(cfg)
-	if err != nil {
-		return nil, fmt.Errorf("init kubernetes client failed: %v", err)
-	}
-
+func NewRBACController(kubeClient *kubernetes.Clientset, kubeCRDClient *crdClient.CRDClient, userCIDR string,
+	userGateway string) (*Controller, error) {
 	o := &Controller{
-		kclient:       client,
+		kclient:       kubeClient,
 		queue:         workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "rbacmanager"),
 		kubeCRDClient: kubeCRDClient,
 		userCIDR:      userCIDR,
diff --git a/pkg/auth-controller/tenant/tenant_controller.go b/pkg/auth-controller/tenant/tenant_controller.go
index 3b750fe..103cb47 100644
--- a/pkg/auth-controller/tenant/tenant_controller.go
+++ b/pkg/auth-controller/tenant/tenant_controller.go
@@ -7,7 +7,6 @@ import (
 	crdClient "git.openstack.org/openstack/stackube/pkg/kubecrd"
 	"git.openstack.org/openstack/stackube/pkg/openstack"
 
-	"git.openstack.org/openstack/stackube/pkg/util"
 	"github.com/golang/glog"
 	apiv1 "k8s.io/api/core/v1"
 	apiextensionsclient "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
@@ -27,38 +26,19 @@ type TenantController struct {
 }
 
 // NewTenantController creates a new tenant controller.
-func NewTenantController(kubeconfig, cloudconfig string) (*TenantController, error) {
-	// Create the client config. Use kubeconfig if given, otherwise assume in-cluster.
-	config, err := util.NewClusterConfig(kubeconfig)
-	if err != nil {
-		return nil, fmt.Errorf("failed to build kubeconfig: %v", err)
-	}
-	clientset, err := apiextensionsclient.NewForConfig(config)
-	if err != nil {
-		return nil, fmt.Errorf("failed to create kubeclient from config: %v", err)
-	}
-
+func NewTenantController(kubeClient *kubernetes.Clientset,
+	osClient *openstack.Client,
+	kubeExtClient *apiextensionsclient.Clientset) (*TenantController, error) {
 	// initialize CRD if it does not exist
-	_, err = crdClient.CreateTenantCRD(clientset)
+	_, err := crdClient.CreateTenantCRD(kubeExtClient)
 	if err != nil && !apierrors.IsAlreadyExists(err) {
 		return nil, fmt.Errorf("failed to create CRD to kube-apiserver: %v", err)
 	}
 
-	k8sClient, err := kubernetes.NewForConfig(config)
-	if err != nil {
-		return nil, fmt.Errorf("failed to create kubernetes client: %v", err)
-	}
-
-	// Create OpenStack client from config
-	openStackClient, err := openstack.NewClient(cloudconfig, kubeconfig)
-	if err != nil {
-		return nil, fmt.Errorf("init openstack client failed: %v", err)
-	}
-
 	c := &TenantController{
-		kubeCRDClient:   openStackClient.CRDClient,
-		k8sClient:       k8sClient,
-		openstackClient: openStackClient,
+		kubeCRDClient:   osClient.CRDClient,
+		k8sClient:       kubeClient,
+		openstackClient: osClient,
 	}
 
 	if err = c.createClusterRoles(); err != nil {
diff --git a/pkg/network-controller/network_controller.go b/pkg/network-controller/network_controller.go
index f70c56a..269e045 100644
--- a/pkg/network-controller/network_controller.go
+++ b/pkg/network-controller/network_controller.go
@@ -3,6 +3,7 @@ package network
 import (
 	"fmt"
 
+	"github.com/golang/glog"
 	apiv1 "k8s.io/api/core/v1"
 	apiextensionsclient "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
 	apierrors "k8s.io/apimachinery/pkg/api/errors"
@@ -11,75 +12,58 @@ import (
 	"k8s.io/client-go/tools/cache"
 
 	crv1 "git.openstack.org/openstack/stackube/pkg/apis/v1"
-	crdClient "git.openstack.org/openstack/stackube/pkg/kubecrd"
-	osDriver "git.openstack.org/openstack/stackube/pkg/openstack"
+	"git.openstack.org/openstack/stackube/pkg/kubecrd"
+	"git.openstack.org/openstack/stackube/pkg/openstack"
 	"git.openstack.org/openstack/stackube/pkg/util"
-
-	"github.com/golang/glog"
 )
 
 // Watcher is an network of watching on resource create/update/delete events
 type NetworkController struct {
-	kubeCRDClient *crdClient.CRDClient
-	driver        *osDriver.Client
+	kubeCRDClient   *kubecrd.CRDClient
+	driver          *openstack.Client
+	networkInformer cache.Controller
 }
 
-func (c *NetworkController) GetKubeCRDClient() *crdClient.CRDClient {
+func (c *NetworkController) GetKubeCRDClient() *kubecrd.CRDClient {
 	return c.kubeCRDClient
 }
 
 func (c *NetworkController) Run(stopCh <-chan struct{}) error {
 	defer utilruntime.HandleCrash()
 
-	source := cache.NewListWatchFromClient(
-		c.kubeCRDClient.Client,
-		crv1.NetworkResourcePlural,
-		apiv1.NamespaceAll,
-		fields.Everything())
-
-	_, networkInformer := cache.NewInformer(
-		source,
-		&crv1.Network{},
-		0,
-		cache.ResourceEventHandlerFuncs{
-			AddFunc:    c.onAdd,
-			UpdateFunc: c.onUpdate,
-			DeleteFunc: c.onDelete,
-		})
-
-	go networkInformer.Run(stopCh)
+	go c.networkInformer.Run(stopCh)
 	<-stopCh
 
 	return nil
 }
 
-func NewNetworkController(kubeconfig, openstackConfigFile string) (*NetworkController, error) {
-	// Create the client config. Use kubeconfig if given, otherwise assume in-cluster.
-	config, err := util.NewClusterConfig(kubeconfig)
-	if err != nil {
-		return nil, fmt.Errorf("failed to build kubeconfig: %v", err)
-	}
-	clientset, err := apiextensionsclient.NewForConfig(config)
-	if err != nil {
-		return nil, fmt.Errorf("failed to create kubeclient from config: %v", err)
-	}
-
+func NewNetworkController(osClient *openstack.Client, kubeExtClient *apiextensionsclient.Clientset) (*NetworkController, error) {
 	// initialize CRD if it does not exist
-	_, err = crdClient.CreateNetworkCRD(clientset)
+	_, err := kubecrd.CreateNetworkCRD(kubeExtClient)
 	if err != nil && !apierrors.IsAlreadyExists(err) {
 		return nil, fmt.Errorf("failed to create CRD to kube-apiserver: %v", err)
 	}
 
-	// Create OpenStack client from config
-	openstack, err := osDriver.NewClient(openstackConfigFile, kubeconfig)
-	if err != nil {
-		return nil, fmt.Errorf("Couldn't initialize openstack: %#v", err)
-	}
-
+	source := cache.NewListWatchFromClient(
+		osClient.CRDClient.Client,
+		crv1.NetworkResourcePlural,
+		apiv1.NamespaceAll,
+		fields.Everything())
 	networkController := &NetworkController{
-		kubeCRDClient: openstack.CRDClient,
-		driver:        openstack,
+		kubeCRDClient: osClient.CRDClient,
+		driver:        osClient,
 	}
+	_, networkInformer := cache.NewInformer(
+		source,
+		&crv1.Network{},
+		0,
+		cache.ResourceEventHandlerFuncs{
+			AddFunc:    networkController.onAdd,
+			UpdateFunc: networkController.onUpdate,
+			DeleteFunc: networkController.onDelete,
+		})
+	networkController.networkInformer = networkInformer
+
 	return networkController, nil
 }
 
diff --git a/pkg/openstack/loadbalancer.go b/pkg/openstack/loadbalancer.go
new file mode 100644
index 0000000..3533a8c
--- /dev/null
+++ b/pkg/openstack/loadbalancer.go
@@ -0,0 +1,766 @@
+package openstack
+
+import (
+	"fmt"
+	"time"
+
+	"k8s.io/apimachinery/pkg/util/wait"
+
+	"github.com/golang/glog"
+	"github.com/gophercloud/gophercloud"
+	"github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/floatingips"
+	"github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/listeners"
+	"github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/loadbalancers"
+	"github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/monitors"
+	"github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/pools"
+	"github.com/gophercloud/gophercloud/pagination"
+)
+
+const (
+	defaultMonitorDelay   = 10
+	defaultMonitorRetry   = 3
+	defaultMonotorTimeout = 3
+
+	// loadbalancerActive* is configuration of exponential backoff for
+	// going into ACTIVE loadbalancer provisioning status. Starting with 1
+	// seconds, multiplying by 1.2 with each step and taking 19 steps at maximum
+	// it will time out after 128s, which roughly corresponds to 120s
+	loadbalancerActiveInitDealy = 1 * time.Second
+	loadbalancerActiveFactor    = 1.2
+	loadbalancerActiveSteps     = 19
+
+	// loadbalancerDelete* is configuration of exponential backoff for
+	// waiting for delete operation to complete. Starting with 1
+	// seconds, multiplying by 1.2 with each step and taking 13 steps at maximum
+	// it will time out after 32s, which roughly corresponds to 30s
+	loadbalancerDeleteInitDealy = 1 * time.Second
+	loadbalancerDeleteFactor    = 1.2
+	loadbalancerDeleteSteps     = 13
+
+	activeStatus = "ACTIVE"
+	errorStatus  = "ERROR"
+)
+
+// LoadBalancer contains all essential information of kubernetes service.
+type LoadBalancer struct {
+	Name            string
+	ServicePort     int
+	TenantID        string
+	SubnetID        string
+	Protocol        string
+	InternalIP      string
+	ExternalIP      string
+	SessionAffinity bool
+	Endpoints       []Endpoint
+}
+
+// Endpoint represents a container endpoint.
+type Endpoint struct {
+	Address string
+	Port    int
+}
+
+// LoadBalancerStatus contains the status of a load balancer.
+type LoadBalancerStatus struct {
+	InternalIP string
+	ExternalIP string
+}
+
+// EnsureLoadBalancer ensures a load balancer is created.
+func (os *Client) EnsureLoadBalancer(lb *LoadBalancer) (*LoadBalancerStatus, error) {
+	// removes old one if already exists.
+	loadbalancer, err := os.getLoadBalanceByName(lb.Name)
+	if err != nil {
+		if isNotFound(err) {
+			// create a new one.
+			lbOpts := loadbalancers.CreateOpts{
+				Name:        lb.Name,
+				Description: "Stackube service",
+				VipSubnetID: lb.SubnetID,
+				TenantID:    lb.TenantID,
+			}
+			loadbalancer, err = loadbalancers.Create(os.Network, lbOpts).Extract()
+			if err != nil {
+				glog.Errorf("Create load balancer %q failed: %v", lb.Name, err)
+				return nil, err
+			}
+		}
+	} else {
+		glog.V(3).Infof("LoadBalancer %s already exists", lb.Name)
+	}
+
+	status, err := os.waitLoadBalancerStatus(loadbalancer.ID)
+	if err != nil {
+		glog.Errorf("Waiting for load balancer provision failed: %v", err)
+		return nil, err
+	}
+
+	glog.V(3).Infof("Load balancer %q becomes %q", lb.Name, status)
+
+	// get old listeners
+	var listener *listeners.Listener
+	oldListeners, err := os.getListenersByLoadBalancerID(loadbalancer.ID)
+	if err != nil {
+		return nil, fmt.Errorf("error getting LB %s listeners: %v", loadbalancer.Name, err)
+	}
+	for i := range oldListeners {
+		l := oldListeners[i]
+		if l.ProtocolPort == lb.ServicePort {
+			listener = &l
+		} else {
+			// delete the obsolete listener
+			if err := os.ensureListenerDeleted(loadbalancer.ID, l); err != nil {
+				return nil, fmt.Errorf("error deleting listener %q: %v", l.Name, err)
+			}
+			os.waitLoadBalancerStatus(loadbalancer.ID)
+		}
+	}
+
+	// create the listener.
+	if listener == nil {
+		lisOpts := listeners.CreateOpts{
+			LoadbalancerID: loadbalancer.ID,
+			// Only tcp is supported now.
+			Protocol:     listeners.ProtocolTCP,
+			ProtocolPort: lb.ServicePort,
+			TenantID:     lb.TenantID,
+			Name:         lb.Name,
+		}
+		listener, err = listeners.Create(os.Network, lisOpts).Extract()
+		if err != nil {
+			glog.Errorf("Create listener %q failed: %v", lb.Name, err)
+			return nil, err
+		}
+		os.waitLoadBalancerStatus(loadbalancer.ID)
+	}
+
+	// create the load balancer pool.
+	pool, err := os.getPoolByListenerID(loadbalancer.ID, listener.ID)
+	if err != nil && !isNotFound(err) {
+		return nil, fmt.Errorf("error getting pool for listener %q: %v", listener.ID, err)
+	}
+	if pool == nil {
+		poolOpts := pools.CreateOpts{
+			Name:       lb.Name,
+			ListenerID: listener.ID,
+			Protocol:   pools.ProtocolTCP,
+			LBMethod:   pools.LBMethodRoundRobin,
+			TenantID:   lb.TenantID,
+		}
+		if lb.SessionAffinity {
+			poolOpts.Persistence = &pools.SessionPersistence{Type: "SOURCE_IP"}
+		}
+		pool, err = pools.Create(os.Network, poolOpts).Extract()
+		if err != nil {
+			glog.Errorf("Create pool %q failed: %v", lb.Name, err)
+			return nil, err
+		}
+		os.waitLoadBalancerStatus(loadbalancer.ID)
+	}
+
+	// create load balancer members.
+	members, err := os.getMembersByPoolID(pool.ID)
+	if err != nil && !isNotFound(err) {
+		return nil, fmt.Errorf("error getting members for pool %q: %v", pool.ID, err)
+	}
+	for _, ep := range lb.Endpoints {
+		if !memberExists(members, ep.Address, ep.Port) {
+			memberName := fmt.Sprintf("%s-%s-%d", lb.Name, ep.Address, ep.Port)
+			_, err = pools.CreateMember(os.Network, pool.ID, pools.CreateMemberOpts{
+				Name:         memberName,
+				ProtocolPort: ep.Port,
+				Address:      ep.Address,
+				SubnetID:     lb.SubnetID,
+			}).Extract()
+			if err != nil {
+				glog.Errorf("Create member %q failed: %v", memberName, err)
+				return nil, err
+			}
+			os.waitLoadBalancerStatus(loadbalancer.ID)
+		} else {
+			members = popMember(members, ep.Address, ep.Port)
+		}
+	}
+	// delete obsolete members
+	for _, member := range members {
+		glog.V(4).Infof("Deleting obsolete member %s for pool %s address %s", member.ID,
+			pool.ID, member.Address)
+		err := pools.DeleteMember(os.Network, pool.ID, member.ID).ExtractErr()
+		if err != nil && !isNotFound(err) {
+			return nil, fmt.Errorf("error deleting member %s for pool %s address %s: %v",
+				member.ID, pool.ID, member.Address, err)
+		}
+	}
+
+	// create loadbalancer monitor.
+	if pool.MonitorID == "" {
+		_, err = monitors.Create(os.Network, monitors.CreateOpts{
+			Name:       lb.Name,
+			Type:       monitors.TypeTCP,
+			PoolID:     pool.ID,
+			TenantID:   lb.TenantID,
+			Delay:      defaultMonitorDelay,
+			Timeout:    defaultMonotorTimeout,
+			MaxRetries: defaultMonitorRetry,
+		}).Extract()
+		if err != nil {
+			glog.Errorf("Create monitor for pool %q failed: %v", pool.ID, err)
+			return nil, err
+		}
+	}
+
+	// associate external IP for the vip.
+	fip, err := os.associateFloatingIP(lb.TenantID, loadbalancer.VipPortID, lb.ExternalIP)
+	if err != nil {
+		glog.Errorf("associateFloatingIP for port %q failed: %v", loadbalancer.VipPortID, err)
+		return nil, err
+	}
+
+	return &LoadBalancerStatus{
+		InternalIP: loadbalancer.VipAddress,
+		ExternalIP: fip,
+	}, nil
+}
+
+// GetLoadBalancer gets a load balancer by name.
+func (os *Client) GetLoadBalancer(name string) (*LoadBalancer, error) {
+	// get load balancer
+	lb, err := os.getLoadBalanceByName(name)
+	if err != nil {
+		return nil, err
+	}
+
+	// get listener
+	listener, err := os.getListenerByName(name)
+	if err != nil {
+		return nil, err
+	}
+
+	// get members
+	endpoints := make([]Endpoint, 0)
+	for _, pool := range listener.Pools {
+		for _, m := range pool.Members {
+			endpoints = append(endpoints, Endpoint{
+				Address: m.Address,
+				Port:    m.ProtocolPort,
+			})
+		}
+	}
+
+	return &LoadBalancer{
+		Name:            lb.Name,
+		ServicePort:     listener.ProtocolPort,
+		TenantID:        lb.TenantID,
+		SubnetID:        lb.VipSubnetID,
+		Protocol:        listener.Protocol,
+		InternalIP:      lb.VipAddress,
+		SessionAffinity: listener.Pools[0].Persistence.Type != "",
+		Endpoints:       endpoints,
+	}, nil
+}
+
+// LoadBalancerExist returns whether a load balancer has already been exist.
+func (os *Client) LoadBalancerExist(name string) (bool, error) {
+	_, err := os.getLoadBalanceByName(name)
+	if err != nil {
+		if isNotFound(err) {
+			return false, nil
+		}
+
+		return false, err
+	}
+
+	return true, nil
+}
+
+// EnsureLoadBalancerDeleted ensures a load balancer is deleted.
+func (os *Client) EnsureLoadBalancerDeleted(name string) error {
+	// get load balancer
+	lb, err := os.getLoadBalanceByName(name)
+	if err != nil {
+		if isNotFound(err) {
+			return nil
+		}
+
+		return err
+	}
+
+	// delete floatingip
+	floatingIP, err := os.getFloatingIPByPortID(lb.VipPortID)
+	if err != nil && !isNotFound(err) {
+		return fmt.Errorf("error getting floating ip by port %q: %v", lb.VipPortID, err)
+	}
+	if floatingIP != nil {
+		err = floatingips.Delete(os.Network, floatingIP.ID).ExtractErr()
+		if err != nil && !isNotFound(err) {
+			return fmt.Errorf("error deleting floating ip %q: %v", floatingIP.ID, err)
+		}
+	}
+
+	// get listeners and corelative pools and members
+	var poolIDs []string
+	var monitorIDs []string
+	var memberIDs []string
+	listenerList, err := os.getListenersByLoadBalancerID(lb.ID)
+	if err != nil {
+		return fmt.Errorf("Error getting load balancer %s listeners: %v", lb.ID, err)
+	}
+
+	for _, listener := range listenerList {
+		pool, err := os.getPoolByListenerID(lb.ID, listener.ID)
+		if err != nil && !isNotFound(err) {
+			return fmt.Errorf("error getting pool for listener %s: %v", listener.ID, err)
+		}
+		poolIDs = append(poolIDs, pool.ID)
+		if pool.MonitorID != "" {
+			monitorIDs = append(monitorIDs, pool.MonitorID)
+		}
+	}
+	for _, pool := range poolIDs {
+		membersList, err := os.getMembersByPoolID(pool)
+		if err != nil && !isNotFound(err) {
+			return fmt.Errorf("Error getting pool members %s: %v", pool, err)
+		}
+		for _, member := range membersList {
+			memberIDs = append(memberIDs, member.ID)
+		}
+	}
+
+	// delete all monitors
+	for _, monitorID := range monitorIDs {
+		err := monitors.Delete(os.Network, monitorID).ExtractErr()
+		if err != nil && !isNotFound(err) {
+			return err
+		}
+		os.waitLoadBalancerStatus(lb.ID)
+	}
+
+	// delete all members and pools
+	for _, poolID := range poolIDs {
+		// delete all members for this pool
+		for _, memberID := range memberIDs {
+			err := pools.DeleteMember(os.Network, poolID, memberID).ExtractErr()
+			if err != nil && !isNotFound(err) {
+				return err
+			}
+			os.waitLoadBalancerStatus(lb.ID)
+		}
+
+		// delete pool
+		err := pools.Delete(os.Network, poolID).ExtractErr()
+		if err != nil && !isNotFound(err) {
+			return err
+		}
+		os.waitLoadBalancerStatus(lb.ID)
+	}
+
+	// delete all listeners
+	for _, listener := range listenerList {
+		err := listeners.Delete(os.Network, listener.ID).ExtractErr()
+		if err != nil && !isNotFound(err) {
+			return err
+		}
+		os.waitLoadBalancerStatus(lb.ID)
+	}
+
+	// delete the load balancer
+	err = loadbalancers.Delete(os.Network, lb.ID).ExtractErr()
+	if err != nil && !isNotFound(err) {
+		return err
+	}
+	os.waitLoadBalancerStatus(lb.ID)
+
+	return nil
+}
+
+func (os *Client) ensureListenerDeleted(loadbalancerID string, listener listeners.Listener) error {
+	for _, pool := range listener.Pools {
+		for _, member := range pool.Members {
+			// delete member
+			if err := pools.DeleteMember(os.Network, pool.ID, member.ID).ExtractErr(); err != nil && !isNotFound(err) {
+				return err
+			}
+			os.waitLoadBalancerStatus(loadbalancerID)
+		}
+
+		// delete monitor
+		if err := monitors.Delete(os.Network, pool.MonitorID).ExtractErr(); err != nil && !isNotFound(err) {
+			return err
+		}
+		os.waitLoadBalancerStatus(loadbalancerID)
+
+		// delete pool
+		if err := pools.Delete(os.Network, pool.ID).ExtractErr(); err != nil && !isNotFound(err) {
+			return err
+		}
+		os.waitLoadBalancerStatus(loadbalancerID)
+	}
+
+	// delete listener
+	if err := listeners.Delete(os.Network, listener.ID).ExtractErr(); err != nil && !isNotFound(err) {
+		return err
+	}
+	os.waitLoadBalancerStatus(loadbalancerID)
+
+	return nil
+}
+
+func (os *Client) waitLoadBalancerStatus(loadbalancerID string) (string, error) {
+	backoff := wait.Backoff{
+		Duration: loadbalancerActiveInitDealy,
+		Factor:   loadbalancerActiveFactor,
+		Steps:    loadbalancerActiveSteps,
+	}
+
+	var provisioningStatus string
+	err := wait.ExponentialBackoff(backoff, func() (bool, error) {
+		loadbalancer, err := loadbalancers.Get(os.Network, loadbalancerID).Extract()
+		if err != nil {
+			return false, err
+		}
+		provisioningStatus = loadbalancer.ProvisioningStatus
+		if loadbalancer.ProvisioningStatus == activeStatus {
+			return true, nil
+		} else if loadbalancer.ProvisioningStatus == errorStatus {
+			return true, fmt.Errorf("Loadbalancer has gone into ERROR state")
+		} else {
+			return false, nil
+		}
+
+	})
+
+	if err == wait.ErrWaitTimeout {
+		err = fmt.Errorf("Loadbalancer failed to go into ACTIVE provisioning status within alloted time")
+	}
+	return provisioningStatus, err
+}
+
+func (os *Client) waitLoadbalancerDeleted(loadbalancerID string) error {
+	backoff := wait.Backoff{
+		Duration: loadbalancerDeleteInitDealy,
+		Factor:   loadbalancerDeleteFactor,
+		Steps:    loadbalancerDeleteSteps,
+	}
+	err := wait.ExponentialBackoff(backoff, func() (bool, error) {
+		_, err := loadbalancers.Get(os.Network, loadbalancerID).Extract()
+		if err != nil {
+			if err == ErrNotFound {
+				return true, nil
+			} else {
+				return false, err
+			}
+		} else {
+			return false, nil
+		}
+	})
+
+	if err == wait.ErrWaitTimeout {
+		err = fmt.Errorf("Loadbalancer failed to delete within the alloted time")
+	}
+
+	return err
+}
+
+func (os *Client) getListenersByLoadBalancerID(id string) ([]listeners.Listener, error) {
+	var existingListeners []listeners.Listener
+	err := listeners.List(os.Network, listeners.ListOpts{LoadbalancerID: id}).EachPage(func(page pagination.Page) (bool, error) {
+		listenerList, err := listeners.ExtractListeners(page)
+		if err != nil {
+			return false, err
+		}
+		for _, l := range listenerList {
+			for _, lb := range l.Loadbalancers {
+				if lb.ID == id {
+					existingListeners = append(existingListeners, l)
+					break
+				}
+			}
+		}
+
+		return true, nil
+	})
+	if err != nil {
+		return nil, err
+	}
+
+	return existingListeners, nil
+}
+
+func (os *Client) getLoadBalanceByName(name string) (*loadbalancers.LoadBalancer, error) {
+	var lb *loadbalancers.LoadBalancer
+
+	opts := loadbalancers.ListOpts{Name: name}
+	pager := loadbalancers.List(os.Network, opts)
+	err := pager.EachPage(func(page pagination.Page) (bool, error) {
+		lbs, err := loadbalancers.ExtractLoadBalancers(page)
+		if err != nil {
+			return false, err
+		}
+
+		switch len(lbs) {
+		case 0:
+			return false, ErrNotFound
+		case 1:
+			lb = &lbs[0]
+			return true, nil
+		default:
+			return false, ErrMultipleResults
+		}
+	})
+	if err != nil {
+		return nil, err
+	}
+
+	if lb == nil {
+		return nil, ErrNotFound
+	}
+
+	return lb, nil
+}
+
+func (os *Client) getPoolByListenerID(loadbalancerID string, listenerID string) (*pools.Pool, error) {
+	listenerPools := make([]pools.Pool, 0, 1)
+	err := pools.List(os.Network, pools.ListOpts{LoadbalancerID: loadbalancerID}).EachPage(
+		func(page pagination.Page) (bool, error) {
+			poolsList, err := pools.ExtractPools(page)
+			if err != nil {
+				return false, err
+			}
+			for _, p := range poolsList {
+				for _, l := range p.Listeners {
+					if l.ID == listenerID {
+						listenerPools = append(listenerPools, p)
+					}
+				}
+			}
+			if len(listenerPools) > 1 {
+				return false, ErrMultipleResults
+			}
+			return true, nil
+		})
+	if err != nil {
+		if isNotFound(err) {
+			return nil, ErrNotFound
+		}
+		return nil, err
+	}
+
+	if len(listenerPools) == 0 {
+		return nil, ErrNotFound
+	} else if len(listenerPools) > 1 {
+		return nil, ErrMultipleResults
+	}
+
+	return &listenerPools[0], nil
+}
+
+// getPoolByName gets openstack pool by name.
+func (os *Client) getPoolByName(name string) (*pools.Pool, error) {
+	var pool *pools.Pool
+
+	opts := pools.ListOpts{Name: name}
+	pager := pools.List(os.Network, opts)
+	err := pager.EachPage(func(page pagination.Page) (bool, error) {
+		ps, err := pools.ExtractPools(page)
+		if err != nil {
+			return false, err
+		}
+
+		switch len(ps) {
+		case 0:
+			return false, ErrNotFound
+		case 1:
+			pool = &ps[0]
+			return true, nil
+		default:
+			return false, ErrMultipleResults
+		}
+	})
+	if err != nil {
+		return nil, err
+	}
+
+	if pool == nil {
+		return nil, ErrNotFound
+	}
+
+	return pool, nil
+}
+
+func (os *Client) getListenerByName(name string) (*listeners.Listener, error) {
+	var listener *listeners.Listener
+
+	opts := listeners.ListOpts{Name: name}
+	pager := listeners.List(os.Network, opts)
+	err := pager.EachPage(func(page pagination.Page) (bool, error) {
+		lists, err := listeners.ExtractListeners(page)
+		if err != nil {
+			return false, err
+		}
+
+		switch len(lists) {
+		case 0:
+			return false, ErrNotFound
+		case 1:
+			listener = &lists[0]
+			return true, nil
+		default:
+			return false, ErrMultipleResults
+		}
+	})
+	if err != nil {
+		return nil, err
+	}
+
+	if listener == nil {
+		return nil, ErrNotFound
+	}
+
+	return listener, nil
+}
+
+func (os *Client) getMembersByPoolID(id string) ([]pools.Member, error) {
+	var members []pools.Member
+	err := pools.ListMembers(os.Network, id, pools.ListMembersOpts{}).EachPage(func(page pagination.Page) (bool, error) {
+		membersList, err := pools.ExtractMembers(page)
+		if err != nil {
+			return false, err
+		}
+		members = append(members, membersList...)
+
+		return true, nil
+	})
+	if err != nil {
+		return nil, err
+	}
+
+	return members, nil
+}
+
+func (os *Client) getFloatingIPByPortID(portID string) (*floatingips.FloatingIP, error) {
+	opts := floatingips.ListOpts{
+		PortID: portID,
+	}
+	pager := floatingips.List(os.Network, opts)
+
+	floatingIPList := make([]floatingips.FloatingIP, 0, 1)
+
+	err := pager.EachPage(func(page pagination.Page) (bool, error) {
+		f, err := floatingips.ExtractFloatingIPs(page)
+		if err != nil {
+			return false, err
+		}
+		floatingIPList = append(floatingIPList, f...)
+		if len(floatingIPList) > 1 {
+			return false, ErrMultipleResults
+		}
+		return true, nil
+	})
+	if err != nil {
+		if isNotFound(err) {
+			return nil, ErrNotFound
+		}
+		return nil, err
+	}
+
+	if len(floatingIPList) == 0 {
+		return nil, ErrNotFound
+	} else if len(floatingIPList) > 1 {
+		return nil, ErrMultipleResults
+	}
+
+	return &floatingIPList[0], nil
+}
+
+// Check if a member exists for node
+func memberExists(members []pools.Member, addr string, port int) bool {
+	for _, member := range members {
+		if member.Address == addr && member.ProtocolPort == port {
+			return true
+		}
+	}
+
+	return false
+}
+
+func (os *Client) associateFloatingIP(tenantID, portID, floatingIPAddress string) (string, error) {
+	var fip *floatingips.FloatingIP
+	opts := floatingips.ListOpts{FloatingIP: floatingIPAddress}
+	pager := floatingips.List(os.Network, opts)
+	err := pager.EachPage(func(page pagination.Page) (bool, error) {
+		floatingipList, err := floatingips.ExtractFloatingIPs(page)
+		if err != nil {
+			return false, err
+		}
+
+		if len(floatingipList) > 0 {
+			fip = &floatingipList[0]
+		}
+
+		return true, nil
+	})
+	if err != nil {
+		return "", err
+	}
+
+	if fip != nil {
+		if fip.PortID != "" {
+			if fip.PortID == portID {
+				glog.V(3).Infof("FIP %q has already been associated with port %q", floatingIPAddress, portID)
+				return fip.FloatingIP, nil
+			}
+			// fip has already been used
+			return fip.FloatingIP, fmt.Errorf("FloatingIP %v is already been binded to %v", floatingIPAddress, fip.PortID)
+		}
+
+		// Update floatingip
+		floatOpts := floatingips.UpdateOpts{PortID: &portID}
+		_, err = floatingips.Update(os.Network, fip.ID, floatOpts).Extract()
+		if err != nil {
+			glog.Errorf("Bind floatingip %v to %v failed: %v", floatingIPAddress, portID, err)
+			return "", err
+		}
+	} else {
+		// Create floatingip
+		opts := floatingips.CreateOpts{
+			FloatingNetworkID: os.ExtNetID,
+			TenantID:          tenantID,
+			FloatingIP:        floatingIPAddress,
+			PortID:            portID,
+		}
+		fip, err = floatingips.Create(os.Network, opts).Extract()
+		if err != nil {
+			glog.Errorf("Create floatingip failed: %v", err)
+			return "", err
+		}
+	}
+
+	return fip.FloatingIP, nil
+}
+
+func popMember(members []pools.Member, addr string, port int) []pools.Member {
+	for i, member := range members {
+		if member.Address == addr && member.ProtocolPort == port {
+			members[i] = members[len(members)-1]
+			members = members[:len(members)-1]
+		}
+	}
+
+	return members
+}
+
+func isNotFound(err error) bool {
+	if err == ErrNotFound {
+		return true
+	}
+
+	if _, ok := err.(*gophercloud.ErrResourceNotFound); ok {
+		return true
+	}
+
+	if _, ok := err.(*gophercloud.ErrDefault404); ok {
+		return true
+	}
+
+	return false
+}
diff --git a/pkg/proxy/helpers.go b/pkg/proxy/helpers.go
index f9ec750..680b882 100644
--- a/pkg/proxy/helpers.go
+++ b/pkg/proxy/helpers.go
@@ -197,15 +197,6 @@ func getServiceHealthCheckNodePort(service *v1.Service) int32 {
 	return service.Spec.HealthCheckNodePort
 }
 
-func loadBalancerStatusDeepCopy(lb *v1.LoadBalancerStatus) *v1.LoadBalancerStatus {
-	c := &v1.LoadBalancerStatus{}
-	c.Ingress = make([]v1.LoadBalancerIngress, len(lb.Ingress))
-	for i := range lb.Ingress {
-		c.Ingress[i] = lb.Ingress[i]
-	}
-	return c
-}
-
 func getRouterNetns(routerID string) string {
 	return "qrouter-" + routerID
 }
diff --git a/pkg/proxy/proxier.go b/pkg/proxy/proxier.go
index e2677a0..debe664 100644
--- a/pkg/proxy/proxier.go
+++ b/pkg/proxy/proxier.go
@@ -24,10 +24,10 @@ import (
 )
 
 const (
-	defaultSyncPerios = 15 * time.Minute
-	minSyncPeriod     = 5 * time.Second
-	syncPeriod        = 30 * time.Second
-	burstSyncs        = 2
+	defaultResyncPeriod = 15 * time.Minute
+	minSyncPeriod       = 5 * time.Second
+	syncPeriod          = 30 * time.Second
+	burstSyncs          = 2
 )
 
 type Proxier struct {
@@ -81,7 +81,7 @@ func NewProxier(kubeConfig, openstackConfig string) (*Proxier, error) {
 		return nil, fmt.Errorf("failed to build clientset: %v", err)
 	}
 
-	factory := informers.NewSharedInformerFactory(clientset, defaultSyncPerios)
+	factory := informers.NewSharedInformerFactory(clientset, defaultResyncPeriod)
 	proxier := &Proxier{
 		kubeClientset:    clientset,
 		osClient:         osClient,
@@ -488,7 +488,7 @@ func (p *Proxier) syncProxyRules() {
 		// Step 3: compose iptables chain.
 		netns := getRouterNetns(nsInfo.router)
 		if !netnsExist(netns) {
-			glog.V(3).Infof("Netns %q doesn't exist, omit the service")
+			glog.V(3).Infof("Netns %q doesn't exist, omit the services in namespace %q", netns, namespace)
 			continue
 		}
 
@@ -522,8 +522,8 @@ func (p *Proxier) syncProxyRules() {
 			svcNameString := svcInfo.serviceNameString
 
 			// Step 5.1: check service type.
-			// Only ClusterIP service is supported now.
-			// TODO: support other types of services.
+			// Only ClusterIP service is supported. NodePort service is not supported since networks are L2 isolated.
+			// LoadBalancer service is handled in service controller.
 			if svcInfo.serviceType != v1.ServiceTypeClusterIP {
 				glog.V(3).Infof("Only ClusterIP service is supported, omitting service %q", svcName.NamespacedName)
 				continue
diff --git a/pkg/proxy/types.go b/pkg/proxy/types.go
index 9128190..a096dd3 100644
--- a/pkg/proxy/types.go
+++ b/pkg/proxy/types.go
@@ -9,6 +9,7 @@ import (
 
 	"github.com/golang/glog"
 
+	"git.openstack.org/openstack/stackube/pkg/util"
 	"k8s.io/api/core/v1"
 	"k8s.io/apimachinery/pkg/types"
 	"k8s.io/apimachinery/pkg/util/sets"
@@ -93,7 +94,7 @@ func newServiceInfo(svcPortName servicePortName, port *v1.ServicePort, service *
 		nodePort:    int(port.NodePort),
 		serviceType: service.Spec.Type,
 		// Deep-copy in case the service instance changes
-		loadBalancerStatus:       *loadBalancerStatusDeepCopy(&service.Status.LoadBalancer),
+		loadBalancerStatus:       *util.LoadBalancerStatusDeepCopy(&service.Status.LoadBalancer),
 		sessionAffinityType:      service.Spec.SessionAffinity,
 		stickyMaxAgeMinutes:      180,
 		externalIPs:              make([]string, len(service.Spec.ExternalIPs)),
diff --git a/pkg/service-controller/helpers.go b/pkg/service-controller/helpers.go
new file mode 100644
index 0000000..767d824
--- /dev/null
+++ b/pkg/service-controller/helpers.go
@@ -0,0 +1,19 @@
+package service
+
+import (
+	"fmt"
+
+	"k8s.io/api/core/v1"
+)
+
+const (
+	lbPrefix = "stackube"
+)
+
+func buildServiceName(service *v1.Service) string {
+	return fmt.Sprintf("%s_%s", service.Namespace, service.Name)
+}
+
+func buildLoadBalancerName(service *v1.Service) string {
+	return fmt.Sprintf("%s_%s_%s", lbPrefix, service.Namespace, service.Name)
+}
diff --git a/pkg/service-controller/service_controller.go b/pkg/service-controller/service_controller.go
new file mode 100644
index 0000000..da7a4d2
--- /dev/null
+++ b/pkg/service-controller/service_controller.go
@@ -0,0 +1,671 @@
+package service
+
+import (
+	"fmt"
+	"reflect"
+	"sync"
+	"time"
+
+	"k8s.io/api/core/v1"
+	"k8s.io/apimachinery/pkg/api/errors"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/util/runtime"
+	"k8s.io/apimachinery/pkg/util/wait"
+	"k8s.io/client-go/informers"
+	informersV1 "k8s.io/client-go/informers/core/v1"
+	"k8s.io/client-go/kubernetes"
+	"k8s.io/client-go/kubernetes/scheme"
+	"k8s.io/client-go/tools/cache"
+	"k8s.io/client-go/util/workqueue"
+
+	"git.openstack.org/openstack/stackube/pkg/openstack"
+	"git.openstack.org/openstack/stackube/pkg/util"
+	"github.com/golang/glog"
+)
+
+const (
+	// Interval of synchronizing service status from apiserver
+	serviceSyncPeriod = 30 * time.Second
+	resyncPeriod      = 5 * time.Minute
+
+	// How long to wait before retrying the processing of a service change.
+	minRetryDelay = 5 * time.Second
+	maxRetryDelay = 300 * time.Second
+
+	clientRetryCount       = 5
+	clientRetryInterval    = 5 * time.Second
+	concurrentServiceSyncs = 1
+	retryable              = true
+	notRetryable           = false
+
+	doNotRetry = time.Duration(0)
+)
+
+type cachedService struct {
+	// The cached state of the service
+	state *v1.Service
+	// Controls error back-off
+	lastRetryDelay time.Duration
+}
+
+type serviceCache struct {
+	mu         sync.Mutex // protects serviceMap
+	serviceMap map[string]*cachedService
+}
+
+type ServiceController struct {
+	cache *serviceCache
+
+	kubeClientset    *kubernetes.Clientset
+	osClient         *openstack.Client
+	factory          informers.SharedInformerFactory
+	serviceInformer  informersV1.ServiceInformer
+	endpointInformer informersV1.EndpointsInformer
+
+	// services that need to be synced
+	workingQueue workqueue.DelayingInterface
+}
+
+// NewServiceController returns a new service controller to keep openstack lbaas resources
+// (load balancers) in sync with the registry.
+func NewServiceController(kubeClient *kubernetes.Clientset,
+	osClient *openstack.Client) (*ServiceController, error) {
+	factory := informers.NewSharedInformerFactory(kubeClient, resyncPeriod)
+	s := &ServiceController{
+		osClient:         osClient,
+		factory:          factory,
+		kubeClientset:    kubeClient,
+		cache:            &serviceCache{serviceMap: make(map[string]*cachedService)},
+		workingQueue:     workqueue.NewNamedDelayingQueue("service"),
+		serviceInformer:  factory.Core().V1().Services(),
+		endpointInformer: factory.Core().V1().Endpoints(),
+	}
+
+	s.serviceInformer.Informer().AddEventHandlerWithResyncPeriod(
+		cache.ResourceEventHandlerFuncs{
+			AddFunc: s.enqueueService,
+			UpdateFunc: func(old, cur interface{}) {
+				oldSvc, ok1 := old.(*v1.Service)
+				curSvc, ok2 := cur.(*v1.Service)
+				if ok1 && ok2 && s.needsUpdate(oldSvc, curSvc) {
+					s.enqueueService(cur)
+				}
+			},
+			DeleteFunc: s.enqueueService,
+		},
+		serviceSyncPeriod,
+	)
+
+	s.endpointInformer.Informer().AddEventHandlerWithResyncPeriod(
+		cache.ResourceEventHandlerFuncs{
+			AddFunc: s.enqueueEndpoints,
+			UpdateFunc: func(old, cur interface{}) {
+				oldEndpoint, ok1 := old.(*v1.Endpoints)
+				curEndpoint, ok2 := cur.(*v1.Endpoints)
+				if ok1 && ok2 && s.needsUpdateEndpoint(oldEndpoint, curEndpoint) {
+					s.enqueueEndpoints(cur)
+				}
+			},
+			DeleteFunc: s.enqueueEndpoints,
+		},
+		serviceSyncPeriod,
+	)
+
+	return s, nil
+}
+
+// obj could be an *v1.Service, or a DeletionFinalStateUnknown marker item.
+func (s *ServiceController) enqueueService(obj interface{}) {
+	key, err := cache.DeletionHandlingMetaNamespaceKeyFunc(obj)
+	if err != nil {
+		glog.Errorf("Couldn't get key for object %#v: %v", obj, err)
+		return
+	}
+	s.workingQueue.Add(key)
+}
+
+// Run starts a background goroutine that watches for changes to services that
+// have (or had) LoadBalancers=true and ensures that they have
+// load balancers created and deleted appropriately.
+// serviceSyncPeriod controls how often we check the cluster's services to
+// ensure that the correct load balancers exist.
+//
+// It's an error to call Run() more than once for a given ServiceController
+// object.
+func (s *ServiceController) Run(stopCh <-chan struct{}) error {
+	defer runtime.HandleCrash()
+	defer s.workingQueue.ShutDown()
+
+	glog.Info("Starting service controller")
+	defer glog.Info("Shutting down service controller")
+
+	go s.factory.Start(stopCh)
+
+	if !cache.WaitForCacheSync(stopCh, s.serviceInformer.Informer().HasSynced) {
+		return fmt.Errorf("failed to cache services")
+	}
+	if !cache.WaitForCacheSync(stopCh, s.endpointInformer.Informer().HasSynced) {
+		return fmt.Errorf("failed to cache endpoints")
+	}
+
+	glog.Infof("Service informer cached")
+
+	for i := 0; i < concurrentServiceSyncs; i++ {
+		go wait.Until(s.worker, time.Second, stopCh)
+	}
+
+	<-stopCh
+	return nil
+}
+
+// obj could be an *v1.Endpoint, or a DeletionFinalStateUnknown marker item.
+func (s *ServiceController) enqueueEndpoints(obj interface{}) {
+	key, err := cache.DeletionHandlingMetaNamespaceKeyFunc(obj)
+	if err != nil {
+		glog.Errorf("Couldn't get key for object %#v: %v", obj, err)
+		return
+	}
+	s.workingQueue.Add(key)
+}
+
+// worker runs a worker thread that just dequeues items, processes them, and marks them done.
+// It enforces that the syncHandler is never invoked concurrently with the same key.
+func (s *ServiceController) worker() {
+	for {
+		func() {
+			key, quit := s.workingQueue.Get()
+			if quit {
+				return
+			}
+			defer s.workingQueue.Done(key)
+			err := s.syncService(key.(string))
+			if err != nil {
+				glog.Errorf("Error syncing service: %v", err)
+			}
+		}()
+	}
+}
+
+// Returns an error if processing the service update failed, along with a time.Duration
+// indicating whether processing should be retried; zero means no-retry; otherwise
+// we should retry in that Duration.
+func (s *ServiceController) processServiceUpdate(cachedService *cachedService, service *v1.Service,
+	key string) (error, time.Duration) {
+	// cache the service, we need the info for service deletion
+	cachedService.state = service
+	err, retry := s.createLoadBalancerIfNeeded(key, service)
+	if err != nil {
+		message := "Error creating load balancer"
+		if retry {
+			message += " (will retry): "
+		} else {
+			message += " (will not retry): "
+		}
+		message += err.Error()
+		glog.V(3).Infof("Create service %q failed: %v, message: %q", buildServiceName(service), err, message)
+
+		return err, cachedService.nextRetryDelay()
+	}
+	// Always update the cache upon success.
+	// NOTE: Since we update the cached service if and only if we successfully
+	// processed it, a cached service being nil implies that it hasn't yet
+	// been successfully processed.
+	s.cache.set(key, cachedService)
+
+	cachedService.resetRetryDelay()
+	return nil, doNotRetry
+}
+
+// Returns whatever error occurred along with a boolean indicator of whether it should be retried.
+func (s *ServiceController) createLoadBalancerIfNeeded(key string, service *v1.Service) (error, bool) {
+	// Save the state so we can avoid a write if it doesn't change
+	previousState := util.LoadBalancerStatusDeepCopy(&service.Status.LoadBalancer)
+	var newState *v1.LoadBalancerStatus
+	var err error
+
+	lbName := buildLoadBalancerName(service)
+	if !wantsLoadBalancer(service) {
+		needDelete := true
+		exists, err := s.osClient.LoadBalancerExist(lbName)
+		if err != nil {
+			return fmt.Errorf("Error getting LB for service %s: %v", key, err), retryable
+		}
+		if !exists {
+			needDelete = false
+		}
+
+		if needDelete {
+			glog.Infof("Deleting existing load balancer for service %s that no longer needs a load balancer.", key)
+			if err := s.osClient.EnsureLoadBalancerDeleted(lbName); err != nil {
+				glog.Errorf("EnsureLoadBalancerDeleted %q failed: %v", lbName, err)
+				return err, retryable
+			}
+		}
+
+		newState = &v1.LoadBalancerStatus{}
+	} else {
+		glog.V(2).Infof("Ensuring LB for service %s", key)
+
+		// The load balancer doesn't exist yet, so create it.
+		newState, err = s.createLoadBalancer(service)
+		if err != nil {
+			return fmt.Errorf("Failed to create load balancer for service %s: %v", key, err), retryable
+		}
+		glog.V(3).Infof("LoadBalancer %q created", lbName)
+	}
+
+	// Write the state if changed
+	// TODO: Be careful here ... what if there were other changes to the service?
+	if !util.LoadBalancerStatusEqual(previousState, newState) {
+		// Make a copy so we don't mutate the shared informer cache
+		copy, err := scheme.Scheme.DeepCopy(service)
+		if err != nil {
+			return err, retryable
+		}
+		service = copy.(*v1.Service)
+
+		// Update the status on the copy
+		service.Status.LoadBalancer = *newState
+
+		if err := s.persistUpdate(service); err != nil {
+			return fmt.Errorf("Failed to persist updated status to apiserver, even after retries. Giving up: %v", err), notRetryable
+		}
+	} else {
+		glog.V(2).Infof("Not persisting unchanged LoadBalancerStatus for service %s to kubernetes.", key)
+	}
+
+	return nil, notRetryable
+}
+
+func (s *ServiceController) persistUpdate(service *v1.Service) error {
+	var err error
+	for i := 0; i < clientRetryCount; i++ {
+		_, err = s.kubeClientset.CoreV1Client.Services(service.Namespace).UpdateStatus(service)
+		if err == nil {
+			return nil
+		}
+		// If the object no longer exists, we don't want to recreate it. Just bail
+		// out so that we can process the delete, which we should soon be receiving
+		// if we haven't already.
+		if errors.IsNotFound(err) {
+			glog.Infof("Not persisting update to service '%s/%s' that no longer exists: %v",
+				service.Namespace, service.Name, err)
+			return nil
+		}
+		// TODO: Try to resolve the conflict if the change was unrelated to load
+		// balancer status. For now, just pass it up the stack.
+		if errors.IsConflict(err) {
+			return fmt.Errorf("Not persisting update to service '%s/%s' that has been changed since we received it: %v",
+				service.Namespace, service.Name, err)
+		}
+		glog.Warningf("Failed to persist updated LoadBalancerStatus to service '%s/%s' after creating its load balancer: %v",
+			service.Namespace, service.Name, err)
+		time.Sleep(clientRetryInterval)
+	}
+	return err
+}
+
+func (s *ServiceController) createLoadBalancer(service *v1.Service) (*v1.LoadBalancerStatus, error) {
+	// Only one protocol and one externalIPs supported per service.
+	if len(service.Spec.ExternalIPs) > 1 {
+		return nil, fmt.Errorf("multiple floatingips are not supported")
+	}
+	if len(service.Spec.Ports) > 1 {
+		return nil, fmt.Errorf("multiple floatingips are not supported")
+	}
+
+	// Only support one network and network's name is same with namespace.
+	networkName := util.BuildNetworkName(service.Namespace, service.Namespace)
+	network, err := s.osClient.GetNetwork(networkName)
+	if err != nil {
+		glog.Errorf("Get network by name %q failed: %v", networkName, err)
+		return nil, err
+	}
+
+	// get endpoints for the service.
+	endpoints, err := s.getEndpoints(service)
+	if err != nil {
+		glog.Errorf("Get endpoints for service %q failed: %v", buildServiceName(service), err)
+		return nil, err
+	}
+
+	// create the loadbalancer.
+	lbName := buildLoadBalancerName(service)
+	svcPort := service.Spec.Ports[0]
+	externalIP := ""
+	if len(service.Spec.ExternalIPs) > 0 {
+		externalIP = service.Spec.ExternalIPs[0]
+	}
+	lb, err := s.osClient.EnsureLoadBalancer(&openstack.LoadBalancer{
+		Name:            lbName,
+		Endpoints:       endpoints,
+		ServicePort:     int(svcPort.Port),
+		TenantID:        network.TenantID,
+		SubnetID:        network.Subnets[0].Uid,
+		Protocol:        string(svcPort.Protocol),
+		ExternalIP:      externalIP,
+		SessionAffinity: service.Spec.SessionAffinity != v1.ServiceAffinityNone,
+	})
+	if err != nil {
+		glog.Errorf("EnsureLoadBalancer %q failed: %v", lbName, err)
+		return nil, err
+	}
+
+	return &v1.LoadBalancerStatus{
+		Ingress: []v1.LoadBalancerIngress{{IP: lb.ExternalIP}},
+	}, nil
+
+}
+
+func (s *ServiceController) getEndpoints(service *v1.Service) ([]openstack.Endpoint, error) {
+	endpoints, err := s.kubeClientset.CoreV1Client.Endpoints(service.Namespace).Get(service.Name, metav1.GetOptions{})
+	if err != nil {
+		return nil, err
+	}
+
+	results := make([]openstack.Endpoint, 0)
+	for i := range endpoints.Subsets {
+		ep := endpoints.Subsets[i]
+		for _, ip := range ep.Addresses {
+			for _, port := range ep.Ports {
+				results = append(results, openstack.Endpoint{
+					Address: ip.IP,
+					Port:    int(port.Port),
+				})
+			}
+		}
+	}
+
+	return results, nil
+}
+
+// ListKeys implements the interface required by DeltaFIFO to list the keys we
+// already know about.
+func (s *serviceCache) ListKeys() []string {
+	s.mu.Lock()
+	defer s.mu.Unlock()
+	keys := make([]string, 0, len(s.serviceMap))
+	for k := range s.serviceMap {
+		keys = append(keys, k)
+	}
+	return keys
+}
+
+// GetByKey returns the value stored in the serviceMap under the given key
+func (s *serviceCache) GetByKey(key string) (interface{}, bool, error) {
+	s.mu.Lock()
+	defer s.mu.Unlock()
+	if v, ok := s.serviceMap[key]; ok {
+		return v, true, nil
+	}
+	return nil, false, nil
+}
+
+// ListKeys implements the interface required by DeltaFIFO to list the keys we
+// already know about.
+func (s *serviceCache) allServices() []*v1.Service {
+	s.mu.Lock()
+	defer s.mu.Unlock()
+	services := make([]*v1.Service, 0, len(s.serviceMap))
+	for _, v := range s.serviceMap {
+		services = append(services, v.state)
+	}
+	return services
+}
+
+func (s *serviceCache) get(serviceName string) (*cachedService, bool) {
+	s.mu.Lock()
+	defer s.mu.Unlock()
+	service, ok := s.serviceMap[serviceName]
+	return service, ok
+}
+
+func (s *serviceCache) getOrCreate(serviceName string) *cachedService {
+	s.mu.Lock()
+	defer s.mu.Unlock()
+	service, ok := s.serviceMap[serviceName]
+	if !ok {
+		service = &cachedService{}
+		s.serviceMap[serviceName] = service
+	}
+	return service
+}
+
+func (s *serviceCache) set(serviceName string, service *cachedService) {
+	s.mu.Lock()
+	defer s.mu.Unlock()
+	s.serviceMap[serviceName] = service
+}
+
+func (s *serviceCache) delete(serviceName string) {
+	s.mu.Lock()
+	defer s.mu.Unlock()
+	delete(s.serviceMap, serviceName)
+}
+
+func (s *ServiceController) needsUpdateEndpoint(oldEndpoint, newEndpoint *v1.Endpoints) bool {
+	if len(oldEndpoint.Subsets) != len(newEndpoint.Subsets) {
+		return true
+	}
+
+	if !reflect.DeepEqual(oldEndpoint.Subsets, newEndpoint.Subsets) {
+		return true
+	}
+
+	return false
+}
+
+func (s *ServiceController) needsUpdate(oldService *v1.Service, newService *v1.Service) bool {
+	if !wantsLoadBalancer(oldService) && !wantsLoadBalancer(newService) {
+		return false
+	}
+	if wantsLoadBalancer(oldService) != wantsLoadBalancer(newService) {
+		glog.V(3).Infof("service %q's type changed to LoadBalancer", buildServiceName(newService))
+		return true
+	}
+
+	if wantsLoadBalancer(newService) && !reflect.DeepEqual(oldService.Spec.LoadBalancerSourceRanges, newService.Spec.LoadBalancerSourceRanges) {
+		glog.V(3).Infof("service %q's LoadBalancerSourceRanges changed: %v -> %v", buildServiceName(newService),
+			oldService.Spec.LoadBalancerSourceRanges, newService.Spec.LoadBalancerSourceRanges)
+		return true
+	}
+
+	if !portsEqualForLB(oldService, newService) || oldService.Spec.SessionAffinity != newService.Spec.SessionAffinity {
+		return true
+	}
+	if !loadBalancerIPsAreEqual(oldService, newService) {
+		glog.V(3).Infof("service %q's LoadbalancerIP changed: %v -> %v", buildServiceName(newService),
+			oldService.Spec.LoadBalancerIP, newService.Spec.LoadBalancerIP)
+		return true
+	}
+	if len(oldService.Spec.ExternalIPs) != len(newService.Spec.ExternalIPs) {
+		glog.V(3).Infof("service %q's ExternalIPs changed: %v -> %v", buildServiceName(newService),
+			len(oldService.Spec.ExternalIPs), len(newService.Spec.ExternalIPs))
+		return true
+	}
+	for i := range oldService.Spec.ExternalIPs {
+		if oldService.Spec.ExternalIPs[i] != newService.Spec.ExternalIPs[i] {
+			glog.V(3).Infof("service %q's ExternalIP added: %v", buildServiceName(newService),
+				newService.Spec.ExternalIPs[i])
+			return true
+		}
+	}
+	if !reflect.DeepEqual(oldService.Annotations, newService.Annotations) {
+		return true
+	}
+	if oldService.UID != newService.UID {
+		glog.V(3).Infof("service %q's UID changed: %v -> %v", buildServiceName(newService),
+			oldService.UID, newService.UID)
+		return true
+	}
+	if oldService.Spec.ExternalTrafficPolicy != newService.Spec.ExternalTrafficPolicy {
+		glog.V(3).Infof("service %q's ExternalTrafficPolicy changed: %v -> %v", buildServiceName(newService),
+			oldService.Spec.ExternalTrafficPolicy, newService.Spec.ExternalTrafficPolicy)
+		return true
+	}
+
+	return false
+}
+
+func getPortsForLB(service *v1.Service) ([]*v1.ServicePort, error) {
+	var protocol v1.Protocol
+
+	ports := []*v1.ServicePort{}
+	for i := range service.Spec.Ports {
+		sp := &service.Spec.Ports[i]
+		// The check on protocol was removed here.  The cloud provider itself is now responsible for all protocol validation
+		ports = append(ports, sp)
+		if protocol == "" {
+			protocol = sp.Protocol
+		} else if protocol != sp.Protocol && wantsLoadBalancer(service) {
+			// TODO:  Convert error messages to use event recorder
+			return nil, fmt.Errorf("mixed protocol external load balancers are not supported.")
+		}
+	}
+	return ports, nil
+}
+
+func portsEqualForLB(x, y *v1.Service) bool {
+	xPorts, err := getPortsForLB(x)
+	if err != nil {
+		return false
+	}
+	yPorts, err := getPortsForLB(y)
+	if err != nil {
+		return false
+	}
+	return portSlicesEqualForLB(xPorts, yPorts)
+}
+
+func portSlicesEqualForLB(x, y []*v1.ServicePort) bool {
+	if len(x) != len(y) {
+		return false
+	}
+
+	for i := range x {
+		if !portEqualForLB(x[i], y[i]) {
+			return false
+		}
+	}
+	return true
+}
+
+func portEqualForLB(x, y *v1.ServicePort) bool {
+	// TODO: Should we check name?  (In theory, an LB could expose it)
+	if x.Name != y.Name {
+		return false
+	}
+
+	if x.Protocol != y.Protocol {
+		return false
+	}
+
+	if x.Port != y.Port {
+		return false
+	}
+
+	// We don't check TargetPort; that is not relevant for load balancing
+	// TODO: Should we blank it out?  Or just check it anyway?
+
+	return true
+}
+
+func wantsLoadBalancer(service *v1.Service) bool {
+	return service.Spec.Type == v1.ServiceTypeLoadBalancer
+}
+
+func loadBalancerIPsAreEqual(oldService, newService *v1.Service) bool {
+	return oldService.Spec.LoadBalancerIP == newService.Spec.LoadBalancerIP
+}
+
+// Computes the next retry, using exponential backoff
+// mutex must be held.
+func (s *cachedService) nextRetryDelay() time.Duration {
+	s.lastRetryDelay = s.lastRetryDelay * 2
+	if s.lastRetryDelay < minRetryDelay {
+		s.lastRetryDelay = minRetryDelay
+	}
+	if s.lastRetryDelay > maxRetryDelay {
+		s.lastRetryDelay = maxRetryDelay
+	}
+	return s.lastRetryDelay
+}
+
+// Resets the retry exponential backoff.  mutex must be held.
+func (s *cachedService) resetRetryDelay() {
+	s.lastRetryDelay = time.Duration(0)
+}
+
+// syncService will sync the Service with the given key if it has had its expectations fulfilled,
+// meaning it did not expect to see any more of its pods created or deleted. This function is not meant to be
+// invoked concurrently with the same key.
+func (s *ServiceController) syncService(key string) error {
+	startTime := time.Now()
+	var cachedService *cachedService
+	var retryDelay time.Duration
+	defer func() {
+		glog.V(4).Infof("Finished syncing service %q (%v)", key, time.Now().Sub(startTime))
+	}()
+
+	namespace, name, err := cache.SplitMetaNamespaceKey(key)
+	if err != nil {
+		return err
+	}
+
+	// service holds the latest service info from apiserver
+	service, err := s.serviceInformer.Lister().Services(namespace).Get(name)
+	switch {
+	case errors.IsNotFound(err):
+		// service absence in store means watcher caught the deletion, ensure LB info is cleaned
+		glog.Infof("Service has been deleted %v", key)
+		err, retryDelay = s.processServiceDeletion(key)
+	case err != nil:
+		glog.Infof("Unable to retrieve service %v from store: %v", key, err)
+		s.workingQueue.Add(key)
+		return err
+	default:
+		cachedService = s.cache.getOrCreate(key)
+		err, retryDelay = s.processServiceUpdate(cachedService, service, key)
+	}
+
+	if retryDelay != 0 {
+		// Add the failed service back to the queue so we'll retry it.
+		glog.Errorf("Failed to process service. Retrying in %s: %v", retryDelay, err)
+		go func(obj interface{}, delay time.Duration) {
+			// put back the service key to working queue, it is possible that more entries of the service
+			// were added into the queue during the delay, but it does not mess as when handling the retry,
+			// it always get the last service info from service store
+			s.workingQueue.AddAfter(obj, delay)
+		}(key, retryDelay)
+	} else if err != nil {
+		runtime.HandleError(fmt.Errorf("Failed to process service. Not retrying: %v", err))
+	}
+	return nil
+}
+
+// Returns an error if processing the service deletion failed, along with a time.Duration
+// indicating whether processing should be retried; zero means no-retry; otherwise
+// we should retry after that Duration.
+func (s *ServiceController) processServiceDeletion(key string) (error, time.Duration) {
+	cachedService, ok := s.cache.get(key)
+	if !ok {
+		return fmt.Errorf("Service %s not in cache even though the watcher thought it was. Ignoring the deletion.", key), doNotRetry
+	}
+	service := cachedService.state
+	// delete load balancer info only if the service type is LoadBalancer
+	if !wantsLoadBalancer(service) {
+		return nil, doNotRetry
+	}
+
+	lbName := buildLoadBalancerName(service)
+	err := s.osClient.EnsureLoadBalancerDeleted(lbName)
+	if err != nil {
+		glog.Errorf("Error deleting load balancer (will retry): %v", err)
+		return err, cachedService.nextRetryDelay()
+	}
+	glog.V(3).Infof("Loadbalancer %q deleted", lbName)
+	s.cache.delete(key)
+
+	cachedService.resetRetryDelay()
+	return nil, doNotRetry
+}
diff --git a/pkg/util/k8s_util.go b/pkg/util/k8s_util.go
index 85ce20f..d89b647 100644
--- a/pkg/util/k8s_util.go
+++ b/pkg/util/k8s_util.go
@@ -4,6 +4,7 @@ import (
 	"fmt"
 	"time"
 
+	"k8s.io/api/core/v1"
 	apiextensionsv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
 	apiextensionsclient "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -67,3 +68,38 @@ func WaitForCRDReady(clientset apiextensionsclient.Interface, crdName string) er
 	}
 	return nil
 }
+
+func LoadBalancerStatusDeepCopy(lb *v1.LoadBalancerStatus) *v1.LoadBalancerStatus {
+	c := &v1.LoadBalancerStatus{}
+	c.Ingress = make([]v1.LoadBalancerIngress, len(lb.Ingress))
+	for i := range lb.Ingress {
+		c.Ingress[i] = lb.Ingress[i]
+	}
+	return c
+}
+
+func LoadBalancerStatusEqual(l, r *v1.LoadBalancerStatus) bool {
+	return ingressSliceEqual(l.Ingress, r.Ingress)
+}
+
+func ingressSliceEqual(lhs, rhs []v1.LoadBalancerIngress) bool {
+	if len(lhs) != len(rhs) {
+		return false
+	}
+	for i := range lhs {
+		if !ingressEqual(&lhs[i], &rhs[i]) {
+			return false
+		}
+	}
+	return true
+}
+
+func ingressEqual(lhs, rhs *v1.LoadBalancerIngress) bool {
+	if lhs.IP != rhs.IP {
+		return false
+	}
+	if lhs.Hostname != rhs.Hostname {
+		return false
+	}
+	return true
+}
diff --git a/pkg/util/util.go b/pkg/util/util.go
index bdfb525..c9c30a4 100644
--- a/pkg/util/util.go
+++ b/pkg/util/util.go
@@ -2,6 +2,7 @@ package util
 
 import (
 	"errors"
+
 	apiv1 "k8s.io/api/core/v1"
 )
 
@@ -19,7 +20,8 @@ var ErrMultipleResults = errors.New("MultipleResults")
 
 func BuildNetworkName(namespace, name string) string {
 	if IsSystemNamespace(namespace) {
-		namespace = SystemTenant
+		// All system namespaces shares same network.
+		return namePrefix + "-" + SystemTenant + "-" + SystemTenant
 	}
 	return namePrefix + "-" + namespace + "-" + name
 }
diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/floatingips/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/floatingips/requests.go
new file mode 100644
index 0000000..8393087
--- /dev/null
+++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/floatingips/requests.go
@@ -0,0 +1,146 @@
+package floatingips
+
+import (
+	"github.com/gophercloud/gophercloud"
+	"github.com/gophercloud/gophercloud/pagination"
+)
+
+// ListOpts allows the filtering and sorting of paginated collections through
+// the API. Filtering is achieved by passing in struct field values that map to
+// the floating IP attributes you want to see returned. SortKey allows you to
+// sort by a particular network attribute. SortDir sets the direction, and is
+// either `asc' or `desc'. Marker and Limit are used for pagination.
+type ListOpts struct {
+	ID                string `q:"id"`
+	FloatingNetworkID string `q:"floating_network_id"`
+	PortID            string `q:"port_id"`
+	FixedIP           string `q:"fixed_ip_address"`
+	FloatingIP        string `q:"floating_ip_address"`
+	TenantID          string `q:"tenant_id"`
+	Limit             int    `q:"limit"`
+	Marker            string `q:"marker"`
+	SortKey           string `q:"sort_key"`
+	SortDir           string `q:"sort_dir"`
+	RouterID          string `q:"router_id"`
+}
+
+// List returns a Pager which allows you to iterate over a collection of
+// floating IP resources. It accepts a ListOpts struct, which allows you to
+// filter and sort the returned collection for greater efficiency.
+func List(c *gophercloud.ServiceClient, opts ListOpts) pagination.Pager {
+	q, err := gophercloud.BuildQueryString(&opts)
+	if err != nil {
+		return pagination.Pager{Err: err}
+	}
+	u := rootURL(c) + q.String()
+	return pagination.NewPager(c, u, func(r pagination.PageResult) pagination.Page {
+		return FloatingIPPage{pagination.LinkedPageBase{PageResult: r}}
+	})
+}
+
+// CreateOptsBuilder is the interface type must satisfy to be used as Create
+// options.
+type CreateOptsBuilder interface {
+	ToFloatingIPCreateMap() (map[string]interface{}, error)
+}
+
+// CreateOpts contains all the values needed to create a new floating IP
+// resource. The only required fields are FloatingNetworkID and PortID which
+// refer to the external network and internal port respectively.
+type CreateOpts struct {
+	FloatingNetworkID string `json:"floating_network_id" required:"true"`
+	FloatingIP        string `json:"floating_ip_address,omitempty"`
+	PortID            string `json:"port_id,omitempty"`
+	FixedIP           string `json:"fixed_ip_address,omitempty"`
+	TenantID          string `json:"tenant_id,omitempty"`
+}
+
+// ToFloatingIPCreateMap allows CreateOpts to satisfy the CreateOptsBuilder
+// interface
+func (opts CreateOpts) ToFloatingIPCreateMap() (map[string]interface{}, error) {
+	return gophercloud.BuildRequestBody(opts, "floatingip")
+}
+
+// Create accepts a CreateOpts struct and uses the values provided to create a
+// new floating IP resource. You can create floating IPs on external networks
+// only. If you provide a FloatingNetworkID which refers to a network that is
+// not external (i.e. its `router:external' attribute is False), the operation
+// will fail and return a 400 error.
+//
+// If you do not specify a FloatingIP address value, the operation will
+// automatically allocate an available address for the new resource. If you do
+// choose to specify one, it must fall within the subnet range for the external
+// network - otherwise the operation returns a 400 error. If the FloatingIP
+// address is already in use, the operation returns a 409 error code.
+//
+// You can associate the new resource with an internal port by using the PortID
+// field. If you specify a PortID that is not valid, the operation will fail and
+// return 404 error code.
+//
+// You must also configure an IP address for the port associated with the PortID
+// you have provided - this is what the FixedIP refers to: an IP fixed to a port.
+// Because a port might be associated with multiple IP addresses, you can use
+// the FixedIP field to associate a particular IP address rather than have the
+// API assume for you. If you specify an IP address that is not valid, the
+// operation will fail and return a 400 error code. If the PortID and FixedIP
+// are already associated with another resource, the operation will fail and
+// returns a 409 error code.
+func Create(c *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) {
+	b, err := opts.ToFloatingIPCreateMap()
+	if err != nil {
+		r.Err = err
+		return
+	}
+	_, r.Err = c.Post(rootURL(c), b, &r.Body, nil)
+	return
+}
+
+// Get retrieves a particular floating IP resource based on its unique ID.
+func Get(c *gophercloud.ServiceClient, id string) (r GetResult) {
+	_, r.Err = c.Get(resourceURL(c, id), &r.Body, nil)
+	return
+}
+
+// UpdateOptsBuilder is the interface type must satisfy to be used as Update
+// options.
+type UpdateOptsBuilder interface {
+	ToFloatingIPUpdateMap() (map[string]interface{}, error)
+}
+
+// UpdateOpts contains the values used when updating a floating IP resource. The
+// only value that can be updated is which internal port the floating IP is
+// linked to. To associate the floating IP with a new internal port, provide its
+// ID. To disassociate the floating IP from all ports, provide an empty string.
+type UpdateOpts struct {
+	PortID *string `json:"port_id"`
+}
+
+// ToFloatingIPUpdateMap allows UpdateOpts to satisfy the UpdateOptsBuilder
+// interface
+func (opts UpdateOpts) ToFloatingIPUpdateMap() (map[string]interface{}, error) {
+	return gophercloud.BuildRequestBody(opts, "floatingip")
+}
+
+// Update allows floating IP resources to be updated. Currently, the only way to
+// "update" a floating IP is to associate it with a new internal port, or
+// disassociated it from all ports. See UpdateOpts for instructions of how to
+// do this.
+func Update(c *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) (r UpdateResult) {
+	b, err := opts.ToFloatingIPUpdateMap()
+	if err != nil {
+		r.Err = err
+		return
+	}
+	_, r.Err = c.Put(resourceURL(c, id), b, &r.Body, &gophercloud.RequestOpts{
+		OkCodes: []int{200},
+	})
+	return
+}
+
+// Delete will permanently delete a particular floating IP resource. Please
+// ensure this is what you want - you can also disassociate the IP from existing
+// internal ports.
+func Delete(c *gophercloud.ServiceClient, id string) (r DeleteResult) {
+	_, r.Err = c.Delete(resourceURL(c, id), nil)
+	return
+}
diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/floatingips/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/floatingips/results.go
new file mode 100644
index 0000000..29d5b56
--- /dev/null
+++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/floatingips/results.go
@@ -0,0 +1,110 @@
+package floatingips
+
+import (
+	"github.com/gophercloud/gophercloud"
+	"github.com/gophercloud/gophercloud/pagination"
+)
+
+// FloatingIP represents a floating IP resource. A floating IP is an external
+// IP address that is mapped to an internal port and, optionally, a specific
+// IP address on a private network. In other words, it enables access to an
+// instance on a private network from an external network. For this reason,
+// floating IPs can only be defined on networks where the `router:external'
+// attribute (provided by the external network extension) is set to True.
+type FloatingIP struct {
+	// Unique identifier for the floating IP instance.
+	ID string `json:"id"`
+
+	// UUID of the external network where the floating IP is to be created.
+	FloatingNetworkID string `json:"floating_network_id"`
+
+	// Address of the floating IP on the external network.
+	FloatingIP string `json:"floating_ip_address"`
+
+	// UUID of the port on an internal network that is associated with the floating IP.
+	PortID string `json:"port_id"`
+
+	// The specific IP address of the internal port which should be associated
+	// with the floating IP.
+	FixedIP string `json:"fixed_ip_address"`
+
+	// Owner of the floating IP. Only admin users can specify a tenant identifier
+	// other than its own.
+	TenantID string `json:"tenant_id"`
+
+	// The condition of the API resource.
+	Status string `json:"status"`
+
+	//The ID of the router used for this Floating-IP
+	RouterID string `json:"router_id"`
+}
+
+type commonResult struct {
+	gophercloud.Result
+}
+
+// Extract a result and extracts a FloatingIP resource.
+func (r commonResult) Extract() (*FloatingIP, error) {
+	var s struct {
+		FloatingIP *FloatingIP `json:"floatingip"`
+	}
+	err := r.ExtractInto(&s)
+	return s.FloatingIP, err
+}
+
+// CreateResult represents the result of a create operation.
+type CreateResult struct {
+	commonResult
+}
+
+// GetResult represents the result of a get operation.
+type GetResult struct {
+	commonResult
+}
+
+// UpdateResult represents the result of an update operation.
+type UpdateResult struct {
+	commonResult
+}
+
+// DeleteResult represents the result of an update operation.
+type DeleteResult struct {
+	gophercloud.ErrResult
+}
+
+// FloatingIPPage is the page returned by a pager when traversing over a
+// collection of floating IPs.
+type FloatingIPPage struct {
+	pagination.LinkedPageBase
+}
+
+// NextPageURL is invoked when a paginated collection of floating IPs has reached
+// the end of a page and the pager seeks to traverse over a new one. In order
+// to do this, it needs to construct the next page's URL.
+func (r FloatingIPPage) NextPageURL() (string, error) {
+	var s struct {
+		Links []gophercloud.Link `json:"floatingips_links"`
+	}
+	err := r.ExtractInto(&s)
+	if err != nil {
+		return "", err
+	}
+	return gophercloud.ExtractNextURL(s.Links)
+}
+
+// IsEmpty checks whether a NetworkPage struct is empty.
+func (r FloatingIPPage) IsEmpty() (bool, error) {
+	is, err := ExtractFloatingIPs(r)
+	return len(is) == 0, err
+}
+
+// ExtractFloatingIPs accepts a Page struct, specifically a FloatingIPPage struct,
+// and extracts the elements into a slice of FloatingIP structs. In other words,
+// a generic collection is mapped into a relevant slice.
+func ExtractFloatingIPs(r pagination.Page) ([]FloatingIP, error) {
+	var s struct {
+		FloatingIPs []FloatingIP `json:"floatingips"`
+	}
+	err := (r.(FloatingIPPage)).ExtractInto(&s)
+	return s.FloatingIPs, err
+}
diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/floatingips/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/floatingips/urls.go
new file mode 100644
index 0000000..1318a18
--- /dev/null
+++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/floatingips/urls.go
@@ -0,0 +1,13 @@
+package floatingips
+
+import "github.com/gophercloud/gophercloud"
+
+const resourcePath = "floatingips"
+
+func rootURL(c *gophercloud.ServiceClient) string {
+	return c.ServiceURL(resourcePath)
+}
+
+func resourceURL(c *gophercloud.ServiceClient, id string) string {
+	return c.ServiceURL(resourcePath, id)
+}
diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/listeners/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/listeners/requests.go
new file mode 100644
index 0000000..4a78447
--- /dev/null
+++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/listeners/requests.go
@@ -0,0 +1,182 @@
+package listeners
+
+import (
+	"github.com/gophercloud/gophercloud"
+	"github.com/gophercloud/gophercloud/pagination"
+)
+
+type Protocol string
+
+// Supported attributes for create/update operations.
+const (
+	ProtocolTCP   Protocol = "TCP"
+	ProtocolHTTP  Protocol = "HTTP"
+	ProtocolHTTPS Protocol = "HTTPS"
+)
+
+// ListOptsBuilder allows extensions to add additional parameters to the
+// List request.
+type ListOptsBuilder interface {
+	ToListenerListQuery() (string, error)
+}
+
+// ListOpts allows the filtering and sorting of paginated collections through
+// the API. Filtering is achieved by passing in struct field values that map to
+// the floating IP attributes you want to see returned. SortKey allows you to
+// sort by a particular listener attribute. SortDir sets the direction, and is
+// either `asc' or `desc'. Marker and Limit are used for pagination.
+type ListOpts struct {
+	ID              string `q:"id"`
+	Name            string `q:"name"`
+	AdminStateUp    *bool  `q:"admin_state_up"`
+	TenantID        string `q:"tenant_id"`
+	LoadbalancerID  string `q:"loadbalancer_id"`
+	DefaultPoolID   string `q:"default_pool_id"`
+	Protocol        string `q:"protocol"`
+	ProtocolPort    int    `q:"protocol_port"`
+	ConnectionLimit int    `q:"connection_limit"`
+	Limit           int    `q:"limit"`
+	Marker          string `q:"marker"`
+	SortKey         string `q:"sort_key"`
+	SortDir         string `q:"sort_dir"`
+}
+
+// ToListenerListQuery formats a ListOpts into a query string.
+func (opts ListOpts) ToListenerListQuery() (string, error) {
+	q, err := gophercloud.BuildQueryString(opts)
+	return q.String(), err
+}
+
+// List returns a Pager which allows you to iterate over a collection of
+// routers. It accepts a ListOpts struct, which allows you to filter and sort
+// the returned collection for greater efficiency.
+//
+// Default policy settings return only those routers that are owned by the
+// tenant who submits the request, unless an admin user submits the request.
+func List(c *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager {
+	url := rootURL(c)
+	if opts != nil {
+		query, err := opts.ToListenerListQuery()
+		if err != nil {
+			return pagination.Pager{Err: err}
+		}
+		url += query
+	}
+	return pagination.NewPager(c, url, func(r pagination.PageResult) pagination.Page {
+		return ListenerPage{pagination.LinkedPageBase{PageResult: r}}
+	})
+}
+
+// CreateOptsBuilder is the interface options structs have to satisfy in order
+// to be used in the main Create operation in this package. Since many
+// extensions decorate or modify the common logic, it is useful for them to
+// satisfy a basic interface in order for them to be used.
+type CreateOptsBuilder interface {
+	ToListenerCreateMap() (map[string]interface{}, error)
+}
+
+// CreateOpts is the common options struct used in this package's Create
+// operation.
+type CreateOpts struct {
+	// The load balancer on which to provision this listener.
+	LoadbalancerID string `json:"loadbalancer_id" required:"true"`
+	// The protocol - can either be TCP, HTTP or HTTPS.
+	Protocol Protocol `json:"protocol" required:"true"`
+	// The port on which to listen for client traffic.
+	ProtocolPort int `json:"protocol_port" required:"true"`
+	// Indicates the owner of the Listener. Required for admins.
+	TenantID string `json:"tenant_id,omitempty"`
+	// Human-readable name for the Listener. Does not have to be unique.
+	Name string `json:"name,omitempty"`
+	// The ID of the default pool with which the Listener is associated.
+	DefaultPoolID string `json:"default_pool_id,omitempty"`
+	// Human-readable description for the Listener.
+	Description string `json:"description,omitempty"`
+	// The maximum number of connections allowed for the Listener.
+	ConnLimit *int `json:"connection_limit,omitempty"`
+	// A reference to a container of TLS secrets.
+	DefaultTlsContainerRef string `json:"default_tls_container_ref,omitempty"`
+	// A list of references to TLS secrets.
+	SniContainerRefs []string `json:"sni_container_refs,omitempty"`
+	// The administrative state of the Listener. A valid value is true (UP)
+	// or false (DOWN).
+	AdminStateUp *bool `json:"admin_state_up,omitempty"`
+}
+
+// ToListenerCreateMap casts a CreateOpts struct to a map.
+func (opts CreateOpts) ToListenerCreateMap() (map[string]interface{}, error) {
+	return gophercloud.BuildRequestBody(opts, "listener")
+}
+
+// Create is an operation which provisions a new Listeners based on the
+// configuration defined in the CreateOpts struct. Once the request is
+// validated and progress has started on the provisioning process, a
+// CreateResult will be returned.
+//
+// Users with an admin role can create Listeners on behalf of other tenants by
+// specifying a TenantID attribute different than their own.
+func Create(c *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) {
+	b, err := opts.ToListenerCreateMap()
+	if err != nil {
+		r.Err = err
+		return
+	}
+	_, r.Err = c.Post(rootURL(c), b, &r.Body, nil)
+	return
+}
+
+// Get retrieves a particular Listeners based on its unique ID.
+func Get(c *gophercloud.ServiceClient, id string) (r GetResult) {
+	_, r.Err = c.Get(resourceURL(c, id), &r.Body, nil)
+	return
+}
+
+// UpdateOptsBuilder is the interface options structs have to satisfy in order
+// to be used in the main Update operation in this package. Since many
+// extensions decorate or modify the common logic, it is useful for them to
+// satisfy a basic interface in order for them to be used.
+type UpdateOptsBuilder interface {
+	ToListenerUpdateMap() (map[string]interface{}, error)
+}
+
+// UpdateOpts is the common options struct used in this package's Update
+// operation.
+type UpdateOpts struct {
+	// Human-readable name for the Listener. Does not have to be unique.
+	Name string `json:"name,omitempty"`
+	// Human-readable description for the Listener.
+	Description string `json:"description,omitempty"`
+	// The maximum number of connections allowed for the Listener.
+	ConnLimit *int `json:"connection_limit,omitempty"`
+	// A reference to a container of TLS secrets.
+	DefaultTlsContainerRef string `json:"default_tls_container_ref,omitempty"`
+	//  A list of references to TLS secrets.
+	SniContainerRefs []string `json:"sni_container_refs,omitempty"`
+	// The administrative state of the Listener. A valid value is true (UP)
+	// or false (DOWN).
+	AdminStateUp *bool `json:"admin_state_up,omitempty"`
+}
+
+// ToListenerUpdateMap casts a UpdateOpts struct to a map.
+func (opts UpdateOpts) ToListenerUpdateMap() (map[string]interface{}, error) {
+	return gophercloud.BuildRequestBody(opts, "listener")
+}
+
+// Update is an operation which modifies the attributes of the specified Listener.
+func Update(c *gophercloud.ServiceClient, id string, opts UpdateOpts) (r UpdateResult) {
+	b, err := opts.ToListenerUpdateMap()
+	if err != nil {
+		r.Err = err
+		return
+	}
+	_, r.Err = c.Put(resourceURL(c, id), b, &r.Body, &gophercloud.RequestOpts{
+		OkCodes: []int{200, 202},
+	})
+	return
+}
+
+// Delete will permanently delete a particular Listeners based on its unique ID.
+func Delete(c *gophercloud.ServiceClient, id string) (r DeleteResult) {
+	_, r.Err = c.Delete(resourceURL(c, id), nil)
+	return
+}
diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/listeners/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/listeners/results.go
new file mode 100644
index 0000000..aa8ed1b
--- /dev/null
+++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/listeners/results.go
@@ -0,0 +1,114 @@
+package listeners
+
+import (
+	"github.com/gophercloud/gophercloud"
+	"github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/pools"
+	"github.com/gophercloud/gophercloud/pagination"
+)
+
+type LoadBalancerID struct {
+	ID string `json:"id"`
+}
+
+// Listener is the primary load balancing configuration object that specifies
+// the loadbalancer and port on which client traffic is received, as well
+// as other details such as the load balancing method to be use, protocol, etc.
+type Listener struct {
+	// The unique ID for the Listener.
+	ID string `json:"id"`
+	// Owner of the Listener. Only an admin user can specify a tenant ID other than its own.
+	TenantID string `json:"tenant_id"`
+	// Human-readable name for the Listener. Does not have to be unique.
+	Name string `json:"name"`
+	// Human-readable description for the Listener.
+	Description string `json:"description"`
+	// The protocol to loadbalance. A valid value is TCP, HTTP, or HTTPS.
+	Protocol string `json:"protocol"`
+	// The port on which to listen to client traffic that is associated with the
+	// Loadbalancer. A valid value is from 0 to 65535.
+	ProtocolPort int `json:"protocol_port"`
+	// The UUID of default pool. Must have compatible protocol with listener.
+	DefaultPoolID string `json:"default_pool_id"`
+	// A list of load balancer IDs.
+	Loadbalancers []LoadBalancerID `json:"loadbalancers"`
+	// The maximum number of connections allowed for the Loadbalancer. Default is -1,
+	// meaning no limit.
+	ConnLimit int `json:"connection_limit"`
+	// The list of references to TLS secrets.
+	SniContainerRefs []string `json:"sni_container_refs"`
+	// Optional. A reference to a container of TLS secrets.
+	DefaultTlsContainerRef string `json:"default_tls_container_ref"`
+	// The administrative state of the Listener. A valid value is true (UP) or false (DOWN).
+	AdminStateUp bool         `json:"admin_state_up"`
+	Pools        []pools.Pool `json:"pools"`
+}
+
+// ListenerPage is the page returned by a pager when traversing over a
+// collection of routers.
+type ListenerPage struct {
+	pagination.LinkedPageBase
+}
+
+// NextPageURL is invoked when a paginated collection of routers has reached
+// the end of a page and the pager seeks to traverse over a new one. In order
+// to do this, it needs to construct the next page's URL.
+func (r ListenerPage) NextPageURL() (string, error) {
+	var s struct {
+		Links []gophercloud.Link `json:"listeners_links"`
+	}
+	err := r.ExtractInto(&s)
+	if err != nil {
+		return "", err
+	}
+	return gophercloud.ExtractNextURL(s.Links)
+}
+
+// IsEmpty checks whether a RouterPage struct is empty.
+func (r ListenerPage) IsEmpty() (bool, error) {
+	is, err := ExtractListeners(r)
+	return len(is) == 0, err
+}
+
+// ExtractListeners accepts a Page struct, specifically a ListenerPage struct,
+// and extracts the elements into a slice of Listener structs. In other words,
+// a generic collection is mapped into a relevant slice.
+func ExtractListeners(r pagination.Page) ([]Listener, error) {
+	var s struct {
+		Listeners []Listener `json:"listeners"`
+	}
+	err := (r.(ListenerPage)).ExtractInto(&s)
+	return s.Listeners, err
+}
+
+type commonResult struct {
+	gophercloud.Result
+}
+
+// Extract is a function that accepts a result and extracts a router.
+func (r commonResult) Extract() (*Listener, error) {
+	var s struct {
+		Listener *Listener `json:"listener"`
+	}
+	err := r.ExtractInto(&s)
+	return s.Listener, err
+}
+
+// CreateResult represents the result of a create operation.
+type CreateResult struct {
+	commonResult
+}
+
+// GetResult represents the result of a get operation.
+type GetResult struct {
+	commonResult
+}
+
+// UpdateResult represents the result of an update operation.
+type UpdateResult struct {
+	commonResult
+}
+
+// DeleteResult represents the result of a delete operation.
+type DeleteResult struct {
+	gophercloud.ErrResult
+}
diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/listeners/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/listeners/urls.go
new file mode 100644
index 0000000..02fb1eb
--- /dev/null
+++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/listeners/urls.go
@@ -0,0 +1,16 @@
+package listeners
+
+import "github.com/gophercloud/gophercloud"
+
+const (
+	rootPath     = "lbaas"
+	resourcePath = "listeners"
+)
+
+func rootURL(c *gophercloud.ServiceClient) string {
+	return c.ServiceURL(rootPath, resourcePath)
+}
+
+func resourceURL(c *gophercloud.ServiceClient, id string) string {
+	return c.ServiceURL(rootPath, resourcePath, id)
+}
diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/loadbalancers/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/loadbalancers/requests.go
new file mode 100644
index 0000000..bc4a3c6
--- /dev/null
+++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/loadbalancers/requests.go
@@ -0,0 +1,172 @@
+package loadbalancers
+
+import (
+	"github.com/gophercloud/gophercloud"
+	"github.com/gophercloud/gophercloud/pagination"
+)
+
+// ListOptsBuilder allows extensions to add additional parameters to the
+// List request.
+type ListOptsBuilder interface {
+	ToLoadBalancerListQuery() (string, error)
+}
+
+// ListOpts allows the filtering and sorting of paginated collections through
+// the API. Filtering is achieved by passing in struct field values that map to
+// the Loadbalancer attributes you want to see returned. SortKey allows you to
+// sort by a particular attribute. SortDir sets the direction, and is
+// either `asc' or `desc'. Marker and Limit are used for pagination.
+type ListOpts struct {
+	Description        string `q:"description"`
+	AdminStateUp       *bool  `q:"admin_state_up"`
+	TenantID           string `q:"tenant_id"`
+	ProvisioningStatus string `q:"provisioning_status"`
+	VipAddress         string `q:"vip_address"`
+	VipPortID          string `q:"vip_port_id"`
+	VipSubnetID        string `q:"vip_subnet_id"`
+	ID                 string `q:"id"`
+	OperatingStatus    string `q:"operating_status"`
+	Name               string `q:"name"`
+	Flavor             string `q:"flavor"`
+	Provider           string `q:"provider"`
+	Limit              int    `q:"limit"`
+	Marker             string `q:"marker"`
+	SortKey            string `q:"sort_key"`
+	SortDir            string `q:"sort_dir"`
+}
+
+// ToLoadbalancerListQuery formats a ListOpts into a query string.
+func (opts ListOpts) ToLoadBalancerListQuery() (string, error) {
+	q, err := gophercloud.BuildQueryString(opts)
+	return q.String(), err
+}
+
+// List returns a Pager which allows you to iterate over a collection of
+// routers. It accepts a ListOpts struct, which allows you to filter and sort
+// the returned collection for greater efficiency.
+//
+// Default policy settings return only those routers that are owned by the
+// tenant who submits the request, unless an admin user submits the request.
+func List(c *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager {
+	url := rootURL(c)
+	if opts != nil {
+		query, err := opts.ToLoadBalancerListQuery()
+		if err != nil {
+			return pagination.Pager{Err: err}
+		}
+		url += query
+	}
+	return pagination.NewPager(c, url, func(r pagination.PageResult) pagination.Page {
+		return LoadBalancerPage{pagination.LinkedPageBase{PageResult: r}}
+	})
+}
+
+// CreateOptsBuilder is the interface options structs have to satisfy in order
+// to be used in the main Create operation in this package. Since many
+// extensions decorate or modify the common logic, it is useful for them to
+// satisfy a basic interface in order for them to be used.
+type CreateOptsBuilder interface {
+	ToLoadBalancerCreateMap() (map[string]interface{}, error)
+}
+
+// CreateOpts is the common options struct used in this package's Create
+// operation.
+type CreateOpts struct {
+	// Optional. Human-readable name for the Loadbalancer. Does not have to be unique.
+	Name string `json:"name,omitempty"`
+	// Optional. Human-readable description for the Loadbalancer.
+	Description string `json:"description,omitempty"`
+	// Required. The network on which to allocate the Loadbalancer's address. A tenant can
+	// only create Loadbalancers on networks authorized by policy (e.g. networks that
+	// belong to them or networks that are shared).
+	VipSubnetID string `json:"vip_subnet_id" required:"true"`
+	// Required for admins. The UUID of the tenant who owns the Loadbalancer.
+	// Only administrative users can specify a tenant UUID other than their own.
+	TenantID string `json:"tenant_id,omitempty"`
+	// Optional. The IP address of the Loadbalancer.
+	VipAddress string `json:"vip_address,omitempty"`
+	// Optional. The administrative state of the Loadbalancer. A valid value is true (UP)
+	// or false (DOWN).
+	AdminStateUp *bool `json:"admin_state_up,omitempty"`
+	// Optional. The UUID of a flavor.
+	Flavor string `json:"flavor,omitempty"`
+	// Optional. The name of the provider.
+	Provider string `json:"provider,omitempty"`
+}
+
+// ToLoadBalancerCreateMap casts a CreateOpts struct to a map.
+func (opts CreateOpts) ToLoadBalancerCreateMap() (map[string]interface{}, error) {
+	return gophercloud.BuildRequestBody(opts, "loadbalancer")
+}
+
+// Create is an operation which provisions a new loadbalancer based on the
+// configuration defined in the CreateOpts struct. Once the request is
+// validated and progress has started on the provisioning process, a
+// CreateResult will be returned.
+//
+// Users with an admin role can create loadbalancers on behalf of other tenants by
+// specifying a TenantID attribute different than their own.
+func Create(c *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) {
+	b, err := opts.ToLoadBalancerCreateMap()
+	if err != nil {
+		r.Err = err
+		return
+	}
+	_, r.Err = c.Post(rootURL(c), b, &r.Body, nil)
+	return
+}
+
+// Get retrieves a particular Loadbalancer based on its unique ID.
+func Get(c *gophercloud.ServiceClient, id string) (r GetResult) {
+	_, r.Err = c.Get(resourceURL(c, id), &r.Body, nil)
+	return
+}
+
+// UpdateOptsBuilder is the interface options structs have to satisfy in order
+// to be used in the main Update operation in this package. Since many
+// extensions decorate or modify the common logic, it is useful for them to
+// satisfy a basic interface in order for them to be used.
+type UpdateOptsBuilder interface {
+	ToLoadBalancerUpdateMap() (map[string]interface{}, error)
+}
+
+// UpdateOpts is the common options struct used in this package's Update
+// operation.
+type UpdateOpts struct {
+	// Optional. Human-readable name for the Loadbalancer. Does not have to be unique.
+	Name string `json:"name,omitempty"`
+	// Optional. Human-readable description for the Loadbalancer.
+	Description string `json:"description,omitempty"`
+	// Optional. The administrative state of the Loadbalancer. A valid value is true (UP)
+	// or false (DOWN).
+	AdminStateUp *bool `json:"admin_state_up,omitempty"`
+}
+
+// ToLoadBalancerUpdateMap casts a UpdateOpts struct to a map.
+func (opts UpdateOpts) ToLoadBalancerUpdateMap() (map[string]interface{}, error) {
+	return gophercloud.BuildRequestBody(opts, "loadbalancer")
+}
+
+// Update is an operation which modifies the attributes of the specified LoadBalancer.
+func Update(c *gophercloud.ServiceClient, id string, opts UpdateOpts) (r UpdateResult) {
+	b, err := opts.ToLoadBalancerUpdateMap()
+	if err != nil {
+		r.Err = err
+		return
+	}
+	_, r.Err = c.Put(resourceURL(c, id), b, &r.Body, &gophercloud.RequestOpts{
+		OkCodes: []int{200, 202},
+	})
+	return
+}
+
+// Delete will permanently delete a particular LoadBalancer based on its unique ID.
+func Delete(c *gophercloud.ServiceClient, id string) (r DeleteResult) {
+	_, r.Err = c.Delete(resourceURL(c, id), nil)
+	return
+}
+
+func GetStatuses(c *gophercloud.ServiceClient, id string) (r GetStatusesResult) {
+	_, r.Err = c.Get(statusRootURL(c, id), &r.Body, nil)
+	return
+}
diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/loadbalancers/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/loadbalancers/results.go
new file mode 100644
index 0000000..4423c24
--- /dev/null
+++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/loadbalancers/results.go
@@ -0,0 +1,125 @@
+package loadbalancers
+
+import (
+	"github.com/gophercloud/gophercloud"
+	"github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/listeners"
+	"github.com/gophercloud/gophercloud/pagination"
+)
+
+// LoadBalancer is the primary load balancing configuration object that specifies
+// the virtual IP address on which client traffic is received, as well
+// as other details such as the load balancing method to be use, protocol, etc.
+type LoadBalancer struct {
+	// Human-readable description for the Loadbalancer.
+	Description string `json:"description"`
+	// The administrative state of the Loadbalancer. A valid value is true (UP) or false (DOWN).
+	AdminStateUp bool `json:"admin_state_up"`
+	// Owner of the LoadBalancer. Only an admin user can specify a tenant ID other than its own.
+	TenantID string `json:"tenant_id"`
+	// The provisioning status of the LoadBalancer. This value is ACTIVE, PENDING_CREATE or ERROR.
+	ProvisioningStatus string `json:"provisioning_status"`
+	// The IP address of the Loadbalancer.
+	VipAddress string `json:"vip_address"`
+	// The UUID of the port associated with the IP address.
+	VipPortID string `json:"vip_port_id"`
+	// The UUID of the subnet on which to allocate the virtual IP for the Loadbalancer address.
+	VipSubnetID string `json:"vip_subnet_id"`
+	// The unique ID for the LoadBalancer.
+	ID string `json:"id"`
+	// The operating status of the LoadBalancer. This value is ONLINE or OFFLINE.
+	OperatingStatus string `json:"operating_status"`
+	// Human-readable name for the LoadBalancer. Does not have to be unique.
+	Name string `json:"name"`
+	// The UUID of a flavor if set.
+	Flavor string `json:"flavor"`
+	// The name of the provider.
+	Provider  string               `json:"provider"`
+	Listeners []listeners.Listener `json:"listeners"`
+}
+
+type StatusTree struct {
+	Loadbalancer *LoadBalancer `json:"loadbalancer"`
+}
+
+// LoadBalancerPage is the page returned by a pager when traversing over a
+// collection of routers.
+type LoadBalancerPage struct {
+	pagination.LinkedPageBase
+}
+
+// NextPageURL is invoked when a paginated collection of routers has reached
+// the end of a page and the pager seeks to traverse over a new one. In order
+// to do this, it needs to construct the next page's URL.
+func (r LoadBalancerPage) NextPageURL() (string, error) {
+	var s struct {
+		Links []gophercloud.Link `json:"loadbalancers_links"`
+	}
+	err := r.ExtractInto(&s)
+	if err != nil {
+		return "", err
+	}
+	return gophercloud.ExtractNextURL(s.Links)
+}
+
+// IsEmpty checks whether a LoadBalancerPage struct is empty.
+func (p LoadBalancerPage) IsEmpty() (bool, error) {
+	is, err := ExtractLoadBalancers(p)
+	return len(is) == 0, err
+}
+
+// ExtractLoadBalancers accepts a Page struct, specifically a LoadbalancerPage struct,
+// and extracts the elements into a slice of LoadBalancer structs. In other words,
+// a generic collection is mapped into a relevant slice.
+func ExtractLoadBalancers(r pagination.Page) ([]LoadBalancer, error) {
+	var s struct {
+		LoadBalancers []LoadBalancer `json:"loadbalancers"`
+	}
+	err := (r.(LoadBalancerPage)).ExtractInto(&s)
+	return s.LoadBalancers, err
+}
+
+type commonResult struct {
+	gophercloud.Result
+}
+
+// Extract is a function that accepts a result and extracts a router.
+func (r commonResult) Extract() (*LoadBalancer, error) {
+	var s struct {
+		LoadBalancer *LoadBalancer `json:"loadbalancer"`
+	}
+	err := r.ExtractInto(&s)
+	return s.LoadBalancer, err
+}
+
+type GetStatusesResult struct {
+	gophercloud.Result
+}
+
+// Extract is a function that accepts a result and extracts a Loadbalancer.
+func (r GetStatusesResult) Extract() (*StatusTree, error) {
+	var s struct {
+		Statuses *StatusTree `json:"statuses"`
+	}
+	err := r.ExtractInto(&s)
+	return s.Statuses, err
+}
+
+// CreateResult represents the result of a create operation.
+type CreateResult struct {
+	commonResult
+}
+
+// GetResult represents the result of a get operation.
+type GetResult struct {
+	commonResult
+}
+
+// UpdateResult represents the result of an update operation.
+type UpdateResult struct {
+	commonResult
+}
+
+// DeleteResult represents the result of a delete operation.
+type DeleteResult struct {
+	gophercloud.ErrResult
+}
diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/loadbalancers/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/loadbalancers/urls.go
new file mode 100644
index 0000000..73cf5dc
--- /dev/null
+++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/loadbalancers/urls.go
@@ -0,0 +1,21 @@
+package loadbalancers
+
+import "github.com/gophercloud/gophercloud"
+
+const (
+	rootPath     = "lbaas"
+	resourcePath = "loadbalancers"
+	statusPath   = "statuses"
+)
+
+func rootURL(c *gophercloud.ServiceClient) string {
+	return c.ServiceURL(rootPath, resourcePath)
+}
+
+func resourceURL(c *gophercloud.ServiceClient, id string) string {
+	return c.ServiceURL(rootPath, resourcePath, id)
+}
+
+func statusRootURL(c *gophercloud.ServiceClient, id string) string {
+	return c.ServiceURL(rootPath, resourcePath, id, statusPath)
+}
diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/monitors/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/monitors/requests.go
new file mode 100644
index 0000000..1e776bf
--- /dev/null
+++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/monitors/requests.go
@@ -0,0 +1,233 @@
+package monitors
+
+import (
+	"fmt"
+
+	"github.com/gophercloud/gophercloud"
+	"github.com/gophercloud/gophercloud/pagination"
+)
+
+// ListOptsBuilder allows extensions to add additional parameters to the
+// List request.
+type ListOptsBuilder interface {
+	ToMonitorListQuery() (string, error)
+}
+
+// ListOpts allows the filtering and sorting of paginated collections through
+// the API. Filtering is achieved by passing in struct field values that map to
+// the Monitor attributes you want to see returned. SortKey allows you to
+// sort by a particular Monitor attribute. SortDir sets the direction, and is
+// either `asc' or `desc'. Marker and Limit are used for pagination.
+type ListOpts struct {
+	ID            string `q:"id"`
+	Name          string `q:"name"`
+	TenantID      string `q:"tenant_id"`
+	PoolID        string `q:"pool_id"`
+	Type          string `q:"type"`
+	Delay         int    `q:"delay"`
+	Timeout       int    `q:"timeout"`
+	MaxRetries    int    `q:"max_retries"`
+	HTTPMethod    string `q:"http_method"`
+	URLPath       string `q:"url_path"`
+	ExpectedCodes string `q:"expected_codes"`
+	AdminStateUp  *bool  `q:"admin_state_up"`
+	Status        string `q:"status"`
+	Limit         int    `q:"limit"`
+	Marker        string `q:"marker"`
+	SortKey       string `q:"sort_key"`
+	SortDir       string `q:"sort_dir"`
+}
+
+// ToMonitorListQuery formats a ListOpts into a query string.
+func (opts ListOpts) ToMonitorListQuery() (string, error) {
+	q, err := gophercloud.BuildQueryString(opts)
+	if err != nil {
+		return "", err
+	}
+	return q.String(), nil
+}
+
+// List returns a Pager which allows you to iterate over a collection of
+// health monitors. It accepts a ListOpts struct, which allows you to filter and sort
+// the returned collection for greater efficiency.
+//
+// Default policy settings return only those health monitors that are owned by the
+// tenant who submits the request, unless an admin user submits the request.
+func List(c *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager {
+	url := rootURL(c)
+	if opts != nil {
+		query, err := opts.ToMonitorListQuery()
+		if err != nil {
+			return pagination.Pager{Err: err}
+		}
+		url += query
+	}
+	return pagination.NewPager(c, url, func(r pagination.PageResult) pagination.Page {
+		return MonitorPage{pagination.LinkedPageBase{PageResult: r}}
+	})
+}
+
+// Constants that represent approved monitoring types.
+const (
+	TypePING  = "PING"
+	TypeTCP   = "TCP"
+	TypeHTTP  = "HTTP"
+	TypeHTTPS = "HTTPS"
+)
+
+var (
+	errDelayMustGETimeout = fmt.Errorf("Delay must be greater than or equal to timeout")
+)
+
+// CreateOptsBuilder is the interface options structs have to satisfy in order
+// to be used in the main Create operation in this package. Since many
+// extensions decorate or modify the common logic, it is useful for them to
+// satisfy a basic interface in order for them to be used.
+type CreateOptsBuilder interface {
+	ToMonitorCreateMap() (map[string]interface{}, error)
+}
+
+// CreateOpts is the common options struct used in this package's Create
+// operation.
+type CreateOpts struct {
+	// Required. The Pool to Monitor.
+	PoolID string `json:"pool_id" required:"true"`
+	// Required. The type of probe, which is PING, TCP, HTTP, or HTTPS, that is
+	// sent by the load balancer to verify the member state.
+	Type string `json:"type" required:"true"`
+	// Required. The time, in seconds, between sending probes to members.
+	Delay int `json:"delay" required:"true"`
+	// Required. Maximum number of seconds for a Monitor to wait for a ping reply
+	// before it times out. The value must be less than the delay value.
+	Timeout int `json:"timeout" required:"true"`
+	// Required. Number of permissible ping failures before changing the member's
+	// status to INACTIVE. Must be a number between 1 and 10.
+	MaxRetries int `json:"max_retries" required:"true"`
+	// Required for HTTP(S) types. URI path that will be accessed if Monitor type
+	// is HTTP or HTTPS.
+	URLPath string `json:"url_path,omitempty"`
+	// Required for HTTP(S) types. The HTTP method used for requests by the
+	// Monitor. If this attribute is not specified, it defaults to "GET".
+	HTTPMethod string `json:"http_method,omitempty"`
+	// Required for HTTP(S) types. Expected HTTP codes for a passing HTTP(S)
+	// Monitor. You can either specify a single status like "200", or a range
+	// like "200-202".
+	ExpectedCodes string `json:"expected_codes,omitempty"`
+	// Indicates the owner of the Loadbalancer. Required for admins.
+	TenantID string `json:"tenant_id,omitempty"`
+	// Optional. The Name of the Monitor.
+	Name         string `json:"name,omitempty"`
+	AdminStateUp *bool  `json:"admin_state_up,omitempty"`
+}
+
+// ToMonitorCreateMap casts a CreateOpts struct to a map.
+func (opts CreateOpts) ToMonitorCreateMap() (map[string]interface{}, error) {
+	b, err := gophercloud.BuildRequestBody(opts, "healthmonitor")
+	if err != nil {
+		return nil, err
+	}
+
+	switch opts.Type {
+	case TypeHTTP, TypeHTTPS:
+		switch opts.URLPath {
+		case "":
+			return nil, fmt.Errorf("URLPath must be provided for HTTP and HTTPS")
+		}
+		switch opts.ExpectedCodes {
+		case "":
+			return nil, fmt.Errorf("ExpectedCodes must be provided for HTTP and HTTPS")
+		}
+	}
+
+	return b, nil
+}
+
+/*
+ Create is an operation which provisions a new Health Monitor. There are
+ different types of Monitor you can provision: PING, TCP or HTTP(S). Below
+ are examples of how to create each one.
+
+ Here is an example config struct to use when creating a PING or TCP Monitor:
+
+ CreateOpts{Type: TypePING, Delay: 20, Timeout: 10, MaxRetries: 3}
+ CreateOpts{Type: TypeTCP, Delay: 20, Timeout: 10, MaxRetries: 3}
+
+ Here is an example config struct to use when creating a HTTP(S) Monitor:
+
+ CreateOpts{Type: TypeHTTP, Delay: 20, Timeout: 10, MaxRetries: 3,
+ HttpMethod: "HEAD", ExpectedCodes: "200", PoolID: "2c946bfc-1804-43ab-a2ff-58f6a762b505"}
+*/
+func Create(c *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) {
+	b, err := opts.ToMonitorCreateMap()
+	if err != nil {
+		r.Err = err
+		return
+	}
+	_, r.Err = c.Post(rootURL(c), b, &r.Body, nil)
+	return
+}
+
+// Get retrieves a particular Health Monitor based on its unique ID.
+func Get(c *gophercloud.ServiceClient, id string) (r GetResult) {
+	_, r.Err = c.Get(resourceURL(c, id), &r.Body, nil)
+	return
+}
+
+// UpdateOptsBuilder is the interface options structs have to satisfy in order
+// to be used in the main Update operation in this package. Since many
+// extensions decorate or modify the common logic, it is useful for them to
+// satisfy a basic interface in order for them to be used.
+type UpdateOptsBuilder interface {
+	ToMonitorUpdateMap() (map[string]interface{}, error)
+}
+
+// UpdateOpts is the common options struct used in this package's Update
+// operation.
+type UpdateOpts struct {
+	// Required. The time, in seconds, between sending probes to members.
+	Delay int `json:"delay,omitempty"`
+	// Required. Maximum number of seconds for a Monitor to wait for a ping reply
+	// before it times out. The value must be less than the delay value.
+	Timeout int `json:"timeout,omitempty"`
+	// Required. Number of permissible ping failures before changing the member's
+	// status to INACTIVE. Must be a number between 1 and 10.
+	MaxRetries int `json:"max_retries,omitempty"`
+	// Required for HTTP(S) types. URI path that will be accessed if Monitor type
+	// is HTTP or HTTPS.
+	URLPath string `json:"url_path,omitempty"`
+	// Required for HTTP(S) types. The HTTP method used for requests by the
+	// Monitor. If this attribute is not specified, it defaults to "GET".
+	HTTPMethod string `json:"http_method,omitempty"`
+	// Required for HTTP(S) types. Expected HTTP codes for a passing HTTP(S)
+	// Monitor. You can either specify a single status like "200", or a range
+	// like "200-202".
+	ExpectedCodes string `json:"expected_codes,omitempty"`
+	// Optional. The Name of the Monitor.
+	Name         string `json:"name,omitempty"`
+	AdminStateUp *bool  `json:"admin_state_up,omitempty"`
+}
+
+// ToMonitorUpdateMap casts a UpdateOpts struct to a map.
+func (opts UpdateOpts) ToMonitorUpdateMap() (map[string]interface{}, error) {
+	return gophercloud.BuildRequestBody(opts, "healthmonitor")
+}
+
+// Update is an operation which modifies the attributes of the specified Monitor.
+func Update(c *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) (r UpdateResult) {
+	b, err := opts.ToMonitorUpdateMap()
+	if err != nil {
+		r.Err = err
+		return
+	}
+
+	_, r.Err = c.Put(resourceURL(c, id), b, &r.Body, &gophercloud.RequestOpts{
+		OkCodes: []int{200, 202},
+	})
+	return
+}
+
+// Delete will permanently delete a particular Monitor based on its unique ID.
+func Delete(c *gophercloud.ServiceClient, id string) (r DeleteResult) {
+	_, r.Err = c.Delete(resourceURL(c, id), nil)
+	return
+}
diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/monitors/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/monitors/results.go
new file mode 100644
index 0000000..05dcf47
--- /dev/null
+++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/monitors/results.go
@@ -0,0 +1,144 @@
+package monitors
+
+import (
+	"github.com/gophercloud/gophercloud"
+	"github.com/gophercloud/gophercloud/pagination"
+)
+
+type PoolID struct {
+	ID string `json:"id"`
+}
+
+// Monitor represents a load balancer health monitor. A health monitor is used
+// to determine whether or not back-end members of the VIP's pool are usable
+// for processing a request. A pool can have several health monitors associated
+// with it. There are different types of health monitors supported:
+//
+// PING: used to ping the members using ICMP.
+// TCP: used to connect to the members using TCP.
+// HTTP: used to send an HTTP request to the member.
+// HTTPS: used to send a secure HTTP request to the member.
+//
+// When a pool has several monitors associated with it, each member of the pool
+// is monitored by all these monitors. If any monitor declares the member as
+// unhealthy, then the member status is changed to INACTIVE and the member
+// won't participate in its pool's load balancing. In other words, ALL monitors
+// must declare the member to be healthy for it to stay ACTIVE.
+type Monitor struct {
+	// The unique ID for the Monitor.
+	ID string `json:"id"`
+
+	// The Name of the Monitor.
+	Name string `json:"name"`
+
+	// Only an administrative user can specify a tenant ID
+	// other than its own.
+	TenantID string `json:"tenant_id"`
+
+	// The type of probe sent by the load balancer to verify the member state,
+	// which is PING, TCP, HTTP, or HTTPS.
+	Type string `json:"type"`
+
+	// The time, in seconds, between sending probes to members.
+	Delay int `json:"delay"`
+
+	// The maximum number of seconds for a monitor to wait for a connection to be
+	// established before it times out. This value must be less than the delay value.
+	Timeout int `json:"timeout"`
+
+	// Number of allowed connection failures before changing the status of the
+	// member to INACTIVE. A valid value is from 1 to 10.
+	MaxRetries int `json:"max_retries"`
+
+	// The HTTP method that the monitor uses for requests.
+	HTTPMethod string `json:"http_method"`
+
+	// The HTTP path of the request sent by the monitor to test the health of a
+	// member. Must be a string beginning with a forward slash (/).
+	URLPath string `json:"url_path" `
+
+	// Expected HTTP codes for a passing HTTP(S) monitor.
+	ExpectedCodes string `json:"expected_codes"`
+
+	// The administrative state of the health monitor, which is up (true) or down (false).
+	AdminStateUp bool `json:"admin_state_up"`
+
+	// The status of the health monitor. Indicates whether the health monitor is
+	// operational.
+	Status string `json:"status"`
+
+	// List of pools that are associated with the health monitor.
+	Pools []PoolID `json:"pools"`
+}
+
+// MonitorPage is the page returned by a pager when traversing over a
+// collection of health monitors.
+type MonitorPage struct {
+	pagination.LinkedPageBase
+}
+
+// NextPageURL is invoked when a paginated collection of monitors has reached
+// the end of a page and the pager seeks to traverse over a new one. In order
+// to do this, it needs to construct the next page's URL.
+func (r MonitorPage) NextPageURL() (string, error) {
+	var s struct {
+		Links []gophercloud.Link `json:"healthmonitors_links"`
+	}
+
+	err := r.ExtractInto(&s)
+	if err != nil {
+		return "", err
+	}
+
+	return gophercloud.ExtractNextURL(s.Links)
+}
+
+// IsEmpty checks whether a MonitorPage struct is empty.
+func (r MonitorPage) IsEmpty() (bool, error) {
+	is, err := ExtractMonitors(r)
+	return len(is) == 0, err
+}
+
+// ExtractMonitors accepts a Page struct, specifically a MonitorPage struct,
+// and extracts the elements into a slice of Monitor structs. In other words,
+// a generic collection is mapped into a relevant slice.
+func ExtractMonitors(r pagination.Page) ([]Monitor, error) {
+	var s struct {
+		Monitors []Monitor `json:"healthmonitors"`
+	}
+	err := (r.(MonitorPage)).ExtractInto(&s)
+	return s.Monitors, err
+}
+
+type commonResult struct {
+	gophercloud.Result
+}
+
+// Extract is a function that accepts a result and extracts a monitor.
+func (r commonResult) Extract() (*Monitor, error) {
+	var s struct {
+		Monitor *Monitor `json:"healthmonitor"`
+	}
+	err := r.ExtractInto(&s)
+	return s.Monitor, err
+}
+
+// CreateResult represents the result of a create operation.
+type CreateResult struct {
+	commonResult
+}
+
+// GetResult represents the result of a get operation.
+type GetResult struct {
+	commonResult
+}
+
+// UpdateResult represents the result of an update operation.
+type UpdateResult struct {
+	commonResult
+}
+
+// DeleteResult represents the result of a delete operation.
+type DeleteResult struct {
+	gophercloud.ErrResult
+}
diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/monitors/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/monitors/urls.go
new file mode 100644
index 0000000..a222e52
--- /dev/null
+++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/monitors/urls.go
@@ -0,0 +1,16 @@
+package monitors
+
+import "github.com/gophercloud/gophercloud"
+
+const (
+	rootPath     = "lbaas"
+	resourcePath = "healthmonitors"
+)
+
+func rootURL(c *gophercloud.ServiceClient) string {
+	return c.ServiceURL(rootPath, resourcePath)
+}
+
+func resourceURL(c *gophercloud.ServiceClient, id string) string {
+	return c.ServiceURL(rootPath, resourcePath, id)
+}
diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/pools/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/pools/requests.go
new file mode 100644
index 0000000..093df6a
--- /dev/null
+++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/pools/requests.go
@@ -0,0 +1,334 @@
+package pools
+
+import (
+	"github.com/gophercloud/gophercloud"
+	"github.com/gophercloud/gophercloud/pagination"
+)
+
+// ListOptsBuilder allows extensions to add additional parameters to the
+// List request.
+type ListOptsBuilder interface {
+	ToPoolListQuery() (string, error)
+}
+
+// ListOpts allows the filtering and sorting of paginated collections through
+// the API. Filtering is achieved by passing in struct field values that map to
+// the Pool attributes you want to see returned. SortKey allows you to
+// sort by a particular Pool attribute. SortDir sets the direction, and is
+// either `asc' or `desc'. Marker and Limit are used for pagination.
+type ListOpts struct {
+	LBMethod       string `q:"lb_algorithm"`
+	Protocol       string `q:"protocol"`
+	TenantID       string `q:"tenant_id"`
+	AdminStateUp   *bool  `q:"admin_state_up"`
+	Name           string `q:"name"`
+	ID             string `q:"id"`
+	LoadbalancerID string `q:"loadbalancer_id"`
+	ListenerID     string `q:"listener_id"`
+	Limit          int    `q:"limit"`
+	Marker         string `q:"marker"`
+	SortKey        string `q:"sort_key"`
+	SortDir        string `q:"sort_dir"`
+}
+
+// ToPoolListQuery formats a ListOpts into a query string.
+func (opts ListOpts) ToPoolListQuery() (string, error) {
+	q, err := gophercloud.BuildQueryString(opts)
+	return q.String(), err
+}
+
+// List returns a Pager which allows you to iterate over a collection of
+// pools. It accepts a ListOpts struct, which allows you to filter and sort
+// the returned collection for greater efficiency.
+//
+// Default policy settings return only those pools that are owned by the
+// tenant who submits the request, unless an admin user submits the request.
+func List(c *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager {
+	url := rootURL(c)
+	if opts != nil {
+		query, err := opts.ToPoolListQuery()
+		if err != nil {
+			return pagination.Pager{Err: err}
+		}
+		url += query
+	}
+	return pagination.NewPager(c, url, func(r pagination.PageResult) pagination.Page {
+		return PoolPage{pagination.LinkedPageBase{PageResult: r}}
+	})
+}
+
+type LBMethod string
+type Protocol string
+
+// Supported attributes for create/update operations.
+const (
+	LBMethodRoundRobin       LBMethod = "ROUND_ROBIN"
+	LBMethodLeastConnections LBMethod = "LEAST_CONNECTIONS"
+	LBMethodSourceIp         LBMethod = "SOURCE_IP"
+
+	ProtocolTCP   Protocol = "TCP"
+	ProtocolHTTP  Protocol = "HTTP"
+	ProtocolHTTPS Protocol = "HTTPS"
+)
+
+// CreateOptsBuilder is the interface options structs have to satisfy in order
+// to be used in the main Create operation in this package. Since many
+// extensions decorate or modify the common logic, it is useful for them to
+// satisfy a basic interface in order for them to be used.
+type CreateOptsBuilder interface {
+	ToPoolCreateMap() (map[string]interface{}, error)
+}
+
+// CreateOpts is the common options struct used in this package's Create
+// operation.
+type CreateOpts struct {
+	// The algorithm used to distribute load between the members of the pool. The
+	// current specification supports LBMethodRoundRobin, LBMethodLeastConnections
+	// and LBMethodSourceIp as valid values for this attribute.
+	LBMethod LBMethod `json:"lb_algorithm" required:"true"`
+	// The protocol used by the pool members, you can use either
+	// ProtocolTCP, ProtocolHTTP, or ProtocolHTTPS.
+	Protocol Protocol `json:"protocol" required:"true"`
+	// The Loadbalancer on which the members of the pool will be associated with.
+	// Note:  one of LoadbalancerID or ListenerID must be provided.
+	LoadbalancerID string `json:"loadbalancer_id,omitempty" xor:"ListenerID"`
+	// The Listener on which the members of the pool will be associated with.
+	// Note:  one of LoadbalancerID or ListenerID must be provided.
+	ListenerID string `json:"listener_id,omitempty" xor:"LoadbalancerID"`
+	// Only required if the caller has an admin role and wants to create a pool
+	// for another tenant.
+	TenantID string `json:"tenant_id,omitempty"`
+	// Name of the pool.
+	Name string `json:"name,omitempty"`
+	// Human-readable description for the pool.
+	Description string `json:"description,omitempty"`
+	// Omit this field to prevent session persistence.
+	Persistence *SessionPersistence `json:"session_persistence,omitempty"`
+	// The administrative state of the Pool. A valid value is true (UP)
+	// or false (DOWN).
+	AdminStateUp *bool `json:"admin_state_up,omitempty"`
+}
+
+// ToPoolCreateMap casts a CreateOpts struct to a map.
+func (opts CreateOpts) ToPoolCreateMap() (map[string]interface{}, error) {
+	return gophercloud.BuildRequestBody(opts, "pool")
+}
+
+// Create accepts a CreateOpts struct and uses the values to create a new
+// load balancer pool.
+func Create(c *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) {
+	b, err := opts.ToPoolCreateMap()
+	if err != nil {
+		r.Err = err
+		return
+	}
+	_, r.Err = c.Post(rootURL(c), b, &r.Body, nil)
+	return
+}
+
+// Get retrieves a particular pool based on its unique ID.
+func Get(c *gophercloud.ServiceClient, id string) (r GetResult) {
+	_, r.Err = c.Get(resourceURL(c, id), &r.Body, nil)
+	return
+}
+
+// UpdateOptsBuilder is the interface options structs have to satisfy in order
+// to be used in the main Update operation in this package. Since many
+// extensions decorate or modify the common logic, it is useful for them to
+// satisfy a basic interface in order for them to be used.
+type UpdateOptsBuilder interface {
+	ToPoolUpdateMap() (map[string]interface{}, error)
+}
+
+// UpdateOpts is the common options struct used in this package's Update
+// operation.
+type UpdateOpts struct {
+	// Name of the pool.
+	Name string `json:"name,omitempty"`
+	// Human-readable description for the pool.
+	Description string `json:"description,omitempty"`
+	// The algorithm used to distribute load between the members of the pool. The
+	// current specification supports LBMethodRoundRobin, LBMethodLeastConnections
+	// and LBMethodSourceIp as valid values for this attribute.
+	LBMethod LBMethod `json:"lb_algorithm,omitempty"`
+	// The administrative state of the Pool. A valid value is true (UP)
+	// or false (DOWN).
+	AdminStateUp *bool `json:"admin_state_up,omitempty"`
+}
+
+// ToPoolUpdateMap casts a UpdateOpts struct to a map.
+func (opts UpdateOpts) ToPoolUpdateMap() (map[string]interface{}, error) {
+	return gophercloud.BuildRequestBody(opts, "pool")
+}
+
+// Update allows pools to be updated.
+func Update(c *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) (r UpdateResult) {
+	b, err := opts.ToPoolUpdateMap()
+	if err != nil {
+		r.Err = err
+		return
+	}
+	_, r.Err = c.Put(resourceURL(c, id), b, &r.Body, &gophercloud.RequestOpts{
+		OkCodes: []int{200},
+	})
+	return
+}
+
+// Delete will permanently delete a particular pool based on its unique ID.
+func Delete(c *gophercloud.ServiceClient, id string) (r DeleteResult) {
+	_, r.Err = c.Delete(resourceURL(c, id), nil)
+	return
+}
+
+// ListMemberOptsBuilder allows extensions to add additional parameters to the
+// ListMembers request.
+type ListMembersOptsBuilder interface {
+	ToMembersListQuery() (string, error)
+}
+
+// ListMembersOpts allows the filtering and sorting of paginated collections through
+// the API. Filtering is achieved by passing in struct field values that map to
+// the Member attributes you want to see returned. SortKey allows you to
+// sort by a particular Member attribute. SortDir sets the direction, and is
+// either `asc' or `desc'. Marker and Limit are used for pagination.
+type ListMembersOpts struct {
+	Name         string `q:"name"`
+	Weight       int    `q:"weight"`
+	AdminStateUp *bool  `q:"admin_state_up"`
+	TenantID     string `q:"tenant_id"`
+	Address      string `q:"address"`
+	ProtocolPort int    `q:"protocol_port"`
+	ID           string `q:"id"`
+	Limit        int    `q:"limit"`
+	Marker       string `q:"marker"`
+	SortKey      string `q:"sort_key"`
+	SortDir      string `q:"sort_dir"`
+}
+
+// ToMemberListQuery formats a ListOpts into a query string.
+func (opts ListMembersOpts) ToMembersListQuery() (string, error) {
+	q, err := gophercloud.BuildQueryString(opts)
+	return q.String(), err
+}
+
+// ListMembers returns a Pager which allows you to iterate over a collection of
+// members. It accepts a ListMembersOptsBuilder, which allows you to filter and sort
+// the returned collection for greater efficiency.
+//
+// Default policy settings return only those members that are owned by the
+// tenant who submits the request, unless an admin user submits the request.
+func ListMembers(c *gophercloud.ServiceClient, poolID string, opts ListMembersOptsBuilder) pagination.Pager {
+	url := memberRootURL(c, poolID)
+	if opts != nil {
+		query, err := opts.ToMembersListQuery()
+		if err != nil {
+			return pagination.Pager{Err: err}
+		}
+		url += query
+	}
+	return pagination.NewPager(c, url, func(r pagination.PageResult) pagination.Page {
+		return MemberPage{pagination.LinkedPageBase{PageResult: r}}
+	})
+}
+
+// CreateMemberOptsBuilder is the interface options structs have to satisfy in order
+// to be used in the CreateMember operation in this package. Since many
+// extensions decorate or modify the common logic, it is useful for them to
+// satisfy a basic interface in order for them to be used.
+type CreateMemberOptsBuilder interface {
+	ToMemberCreateMap() (map[string]interface{}, error)
+}
+
+// CreateMemberOpts is the common options struct used in this package's CreateMember
+// operation.
+type CreateMemberOpts struct {
+	// Required. The IP address of the member to receive traffic from the load balancer.
+	Address string `json:"address" required:"true"`
+	// Required. The port on which to listen for client traffic.
+	ProtocolPort int `json:"protocol_port" required:"true"`
+	// Optional. Name of the Member.
+	Name string `json:"name,omitempty"`
+	// Only required if the caller has an admin role and wants to create a Member
+	// for another tenant.
+	TenantID string `json:"tenant_id,omitempty"`
+	// Optional. A positive integer value that indicates the relative portion of
+	// traffic that this member should receive from the pool. For example, a
+	// member with a weight of 10 receives five times as much traffic as a member
+	// with a weight of 2.
+	Weight int `json:"weight,omitempty"`
+	// Optional.  If you omit this parameter, LBaaS uses the vip_subnet_id
+	// parameter value for the subnet UUID.
+	SubnetID string `json:"subnet_id,omitempty"`
+	// Optional. The administrative state of the Pool. A valid value is true (UP)
+	// or false (DOWN).
+	AdminStateUp *bool `json:"admin_state_up,omitempty"`
+}
+
+// ToMemberCreateMap casts a CreateOpts struct to a map.
+func (opts CreateMemberOpts) ToMemberCreateMap() (map[string]interface{}, error) {
+	return gophercloud.BuildRequestBody(opts, "member")
+}
+
+// CreateMember will create and associate a Member with a particular Pool.
+func CreateMember(c *gophercloud.ServiceClient, poolID string, opts CreateMemberOpts) (r CreateMemberResult) {
+	b, err := opts.ToMemberCreateMap()
+	if err != nil {
+		r.Err = err
+		return
+	}
+	_, r.Err = c.Post(memberRootURL(c, poolID), b, &r.Body, nil)
+	return
+}
+
+// GetMember retrieves a particular Pool Member based on its unique ID.
+func GetMember(c *gophercloud.ServiceClient, poolID string, memberID string) (r GetMemberResult) {
+	_, r.Err = c.Get(memberResourceURL(c, poolID, memberID), &r.Body, nil)
+	return
+}
+
+// MemberUpdateOptsBuilder is the interface options structs have to satisfy in order
+// to be used in the main Update operation in this package. Since many
+// extensions decorate or modify the common logic, it is useful for them to
+// satisfy a basic interface in order for them to be used.
+type UpdateMemberOptsBuilder interface {
+	ToMemberUpdateMap() (map[string]interface{}, error)
+}
+
+// UpdateMemberOpts is the common options struct used in this package's Update
+// operation.
+type UpdateMemberOpts struct {
+	// Optional. Name of the Member.
+	Name string `json:"name,omitempty"`
+	// Optional. A positive integer value that indicates the relative portion of
+	// traffic that this member should receive from the pool. For example, a
+	// member with a weight of 10 receives five times as much traffic as a member
+	// with a weight of 2.
+	Weight int `json:"weight,omitempty"`
+	// Optional. The administrative state of the Pool. A valid value is true (UP)
+	// or false (DOWN).
+	AdminStateUp *bool `json:"admin_state_up,omitempty"`
+}
+
+// ToMemberUpdateMap casts a UpdateOpts struct to a map.
+func (opts UpdateMemberOpts) ToMemberUpdateMap() (map[string]interface{}, error) {
+	return gophercloud.BuildRequestBody(opts, "member")
+}
+
+// Update allows Member to be updated.
+func UpdateMember(c *gophercloud.ServiceClient, poolID string, memberID string, opts UpdateMemberOptsBuilder) (r UpdateMemberResult) {
+	b, err := opts.ToMemberUpdateMap()
+	if err != nil {
+		r.Err = err
+		return
+	}
+	_, r.Err = c.Put(memberResourceURL(c, poolID, memberID), b, &r.Body, &gophercloud.RequestOpts{
+		OkCodes: []int{200, 201, 202},
+	})
+	return
+}
+
+// DisassociateMember will remove and disassociate a Member from a particular Pool.
+func DeleteMember(c *gophercloud.ServiceClient, poolID string, memberID string) (r DeleteMemberResult) {
+	_, r.Err = c.Delete(memberResourceURL(c, poolID, memberID), nil)
+	return
+}
diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/pools/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/pools/results.go
new file mode 100644
index 0000000..0e0bf36
--- /dev/null
+++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/pools/results.go
@@ -0,0 +1,242 @@
+package pools
+
+import (
+	"github.com/gophercloud/gophercloud"
+	"github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/monitors"
+	"github.com/gophercloud/gophercloud/pagination"
+)
+
+// SessionPersistence represents the session persistence feature of the load
+// balancing service. It attempts to force connections or requests in the same
+// session to be processed by the same member as long as it is ative. Three
+// types of persistence are supported:
+//
+// SOURCE_IP:   With this mode, all connections originating from the same source
+//              IP address, will be handled by the same Member of the Pool.
+// HTTP_COOKIE: With this persistence mode, the load balancing function will
+//              create a cookie on the first request from a client. Subsequent
+//              requests containing the same cookie value will be handled by
+//              the same Member of the Pool.
+// APP_COOKIE:  With this persistence mode, the load balancing function will
+//              rely on a cookie established by the backend application. All
+//              requests carrying the same cookie value will be handled by the
+//              same Member of the Pool.
+type SessionPersistence struct {
+	// The type of persistence mode
+	Type string `json:"type"`
+
+	// Name of cookie if persistence mode is set appropriately
+	CookieName string `json:"cookie_name,omitempty"`
+}
+
+type LoadBalancerID struct {
+	ID string `json:"id"`
+}
+
+type ListenerID struct {
+	ID string `json:"id"`
+}
+
+// Pool represents a logical set of devices, such as web servers, that you
+// group together to receive and process traffic. The load balancing function
+// chooses a Member of the Pool according to the configured load balancing
+// method to handle the new requests or connections received on the VIP address.
+type Pool struct {
+	// The load-balancer algorithm, which is round-robin, least-connections, and
+	// so on. This value, which must be supported, is dependent on the provider.
+	// Round-robin must be supported.
+	LBMethod string `json:"lb_algorithm"`
+	// The protocol of the Pool, which is TCP, HTTP, or HTTPS.
+	Protocol string `json:"protocol"`
+	// Description for the Pool.
+	Description string `json:"description"`
+	// A list of listeners objects IDs.
+	Listeners []ListenerID `json:"listeners"` //[]map[string]interface{}
+	// A list of member objects IDs.
+	Members []Member `json:"members"`
+	// The ID of associated health monitor.
+	MonitorID string `json:"healthmonitor_id"`
+	// The network on which the members of the Pool will be located. Only members
+	// that are on this network can be added to the Pool.
+	SubnetID string `json:"subnet_id"`
+	// Owner of the Pool. Only an administrative user can specify a tenant ID
+	// other than its own.
+	TenantID string `json:"tenant_id"`
+	// The administrative state of the Pool, which is up (true) or down (false).
+	AdminStateUp bool `json:"admin_state_up"`
+	// Pool name. Does not have to be unique.
+	Name string `json:"name"`
+	// The unique ID for the Pool.
+	ID string `json:"id"`
+	// A list of load balancer objects IDs.
+	Loadbalancers []LoadBalancerID `json:"loadbalancers"`
+	// Indicates whether connections in the same session will be processed by the
+	// same Pool member or not.
+	Persistence SessionPersistence `json:"session_persistence"`
+	// The provider
+	Provider string           `json:"provider"`
+	Monitor  monitors.Monitor `json:"healthmonitor"`
+}
+
+// PoolPage is the page returned by a pager when traversing over a
+// collection of pools.
+type PoolPage struct {
+	pagination.LinkedPageBase
+}
+
+// NextPageURL is invoked when a paginated collection of pools has reached
+// the end of a page and the pager seeks to traverse over a new one. In order
+// to do this, it needs to construct the next page's URL.
+func (r PoolPage) NextPageURL() (string, error) {
+	var s struct {
+		Links []gophercloud.Link `json:"pools_links"`
+	}
+	err := r.ExtractInto(&s)
+	if err != nil {
+		return "", err
+	}
+	return gophercloud.ExtractNextURL(s.Links)
+}
+
+// IsEmpty checks whether a PoolPage struct is empty.
+func (r PoolPage) IsEmpty() (bool, error) {
+	is, err := ExtractPools(r)
+	return len(is) == 0, err
+}
+
+// ExtractPools accepts a Page struct, specifically a PoolPage struct,
+// and extracts the elements into a slice of Router structs. In other words,
+// a generic collection is mapped into a relevant slice.
+func ExtractPools(r pagination.Page) ([]Pool, error) {
+	var s struct {
+		Pools []Pool `json:"pools"`
+	}
+	err := (r.(PoolPage)).ExtractInto(&s)
+	return s.Pools, err
+}
+
+type commonResult struct {
+	gophercloud.Result
+}
+
+// Extract is a function that accepts a result and extracts a router.
+func (r commonResult) Extract() (*Pool, error) {
+	var s struct {
+		Pool *Pool `json:"pool"`
+	}
+	err := r.ExtractInto(&s)
+	return s.Pool, err
+}
+
+// CreateResult represents the result of a Create operation.
+type CreateResult struct {
+	commonResult
+}
+
+// GetResult represents the result of a Get operation.
+type GetResult struct {
+	commonResult
+}
+
+// UpdateResult represents the result of an Update operation.
+type UpdateResult struct {
+	commonResult
+}
+
+// DeleteResult represents the result of a Delete operation.
+type DeleteResult struct {
+	gophercloud.ErrResult
+}
+
+// Member represents the application running on a backend server.
+type Member struct {
+	// Name of the Member.
+	Name string `json:"name"`
+	// Weight of Member.
+	Weight int `json:"weight"`
+	// The administrative state of the member, which is up (true) or down (false).
+	AdminStateUp bool `json:"admin_state_up"`
+	// Owner of the Member. Only an administrative user can specify a tenant ID
+	// other than its own.
+	TenantID string `json:"tenant_id"`
+	// parameter value for the subnet UUID.
+	SubnetID string `json:"subnet_id"`
+	// The Pool to which the Member belongs.
+	PoolID string `json:"pool_id"`
+	// The IP address of the Member.
+	Address string `json:"address"`
+	// The port on which the application is hosted.
+	ProtocolPort int `json:"protocol_port"`
+	// The unique ID for the Member.
+	ID string `json:"id"`
+}
+
+// MemberPage is the page returned by a pager when traversing over a
+// collection of Members in a Pool.
+type MemberPage struct {
+	pagination.LinkedPageBase
+}
+
+// NextPageURL is invoked when a paginated collection of members has reached
+// the end of a page and the pager seeks to traverse over a new one. In order
+// to do this, it needs to construct the next page's URL.
+func (r MemberPage) NextPageURL() (string, error) {
+	var s struct {
+		Links []gophercloud.Link `json:"members_links"`
+	}
+	err := r.ExtractInto(&s)
+	if err != nil {
+		return "", err
+	}
+	return gophercloud.ExtractNextURL(s.Links)
+}
+
+// IsEmpty checks whether a MemberPage struct is empty.
+func (r MemberPage) IsEmpty() (bool, error) {
+	is, err := ExtractMembers(r)
+	return len(is) == 0, err
+}
+
+// ExtractMembers accepts a Page struct, specifically a RouterPage struct,
+// and extracts the elements into a slice of Router structs. In other words,
+// a generic collection is mapped into a relevant slice.
+func ExtractMembers(r pagination.Page) ([]Member, error) {
+	var s struct {
+		Members []Member `json:"members"`
+	}
+	err := (r.(MemberPage)).ExtractInto(&s)
+	return s.Members, err
+}
+
+type commonMemberResult struct {
+	gophercloud.Result
+}
+
+// ExtractMember is a function that accepts a result and extracts a router.
+func (r commonMemberResult) Extract() (*Member, error) {
+	var s struct {
+		Member *Member `json:"member"`
+	}
+	err := r.ExtractInto(&s)
+	return s.Member, err
+}
+
+// CreateMemberResult represents the result of a CreateMember operation.
+type CreateMemberResult struct {
+	commonMemberResult
+}
+
+// GetMemberResult represents the result of a GetMember operation.
+type GetMemberResult struct {
+	commonMemberResult
+}
+
+// UpdateMemberResult represents the result of an UpdateMember operation.
+type UpdateMemberResult struct {
+	commonMemberResult
+}
+
+// DeleteMemberResult represents the result of a DeleteMember operation.
+type DeleteMemberResult struct {
+	gophercloud.ErrResult
+}
diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/pools/urls.go b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/pools/urls.go
new file mode 100644
index 0000000..bceca67
--- /dev/null
+++ b/vendor/github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/pools/urls.go
@@ -0,0 +1,25 @@
+package pools
+
+import "github.com/gophercloud/gophercloud"
+
+const (
+	rootPath     = "lbaas"
+	resourcePath = "pools"
+	memberPath   = "members"
+)
+
+func rootURL(c *gophercloud.ServiceClient) string {
+	return c.ServiceURL(rootPath, resourcePath)
+}
+
+func resourceURL(c *gophercloud.ServiceClient, id string) string {
+	return c.ServiceURL(rootPath, resourcePath, id)
+}
+
+func memberRootURL(c *gophercloud.ServiceClient, poolId string) string {
+	return c.ServiceURL(rootPath, resourcePath, poolId, memberPath)
+}
+
+func memberResourceURL(c *gophercloud.ServiceClient, poolID string, memeberID string) string {
+	return c.ServiceURL(rootPath, resourcePath, poolID, memberPath, memeberID)
+}
diff --git a/vendor/vendor.json b/vendor/vendor.json
index a861210..2fd3d78 100644
--- a/vendor/vendor.json
+++ b/vendor/vendor.json
@@ -239,12 +239,42 @@
 			"revision": "1217c40cf502cef2fc3aa1a25fcd1ed1bdf00a4b",
 			"revisionTime": "2017-07-18T07:00:34Z"
 		},
+		{
+			"checksumSHA1": "CHmnyRSFPivC+b/ojgfeEIY5ReM=",
+			"path": "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/floatingips",
+			"revision": "1217c40cf502cef2fc3aa1a25fcd1ed1bdf00a4b",
+			"revisionTime": "2017-07-18T07:00:34Z"
+		},
 		{
 			"checksumSHA1": "Mjt7GwFygyqPxygY8xZZnUasHmk=",
 			"path": "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/routers",
 			"revision": "1217c40cf502cef2fc3aa1a25fcd1ed1bdf00a4b",
 			"revisionTime": "2017-07-18T07:00:34Z"
 		},
+		{
+			"checksumSHA1": "mhpwj5tPv7Uw5aUfC55fhLPBcKo=",
+			"path": "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/listeners",
+			"revision": "1217c40cf502cef2fc3aa1a25fcd1ed1bdf00a4b",
+			"revisionTime": "2017-07-18T07:00:34Z"
+		},
+		{
+			"checksumSHA1": "5efJz6UH7JCFeav5ZCCzicXCFTU=",
+			"path": "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/loadbalancers",
+			"revision": "1217c40cf502cef2fc3aa1a25fcd1ed1bdf00a4b",
+			"revisionTime": "2017-07-18T07:00:34Z"
+		},
+		{
+			"checksumSHA1": "TVFgBTz7B6bb1R4TWdgAkbE1/fk=",
+			"path": "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/monitors",
+			"revision": "1217c40cf502cef2fc3aa1a25fcd1ed1bdf00a4b",
+			"revisionTime": "2017-07-18T07:00:34Z"
+		},
+		{
+			"checksumSHA1": "xirjw9vJIN6rmkT3T56bfPfOLUM=",
+			"path": "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/pools",
+			"revision": "1217c40cf502cef2fc3aa1a25fcd1ed1bdf00a4b",
+			"revisionTime": "2017-07-18T07:00:34Z"
+		},
 		{
 			"checksumSHA1": "S+9nmGWO/5zr/leEGq4qLzU0gsQ=",
 			"path": "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/portsbinding",