func (a *Allocator) allocateService(ctx context.Context, nc *networkContext, s *api.Service) error { if s.Spec.Endpoint != nil { if s.Endpoint == nil { s.Endpoint = &api.Endpoint{ Spec: s.Spec.Endpoint.Copy(), } } // The service is trying to expose ports to the external // world. Automatically attach the service to the ingress // network only if it is not already done. if len(s.Spec.Endpoint.Ports) != 0 { var found bool for _, vip := range s.Endpoint.VirtualIPs { if vip.NetworkID == ingressNetwork.ID { found = true break } } if !found { s.Endpoint.VirtualIPs = append(s.Endpoint.VirtualIPs, &api.Endpoint_VirtualIP{NetworkID: ingressNetwork.ID}) } } } if err := nc.nwkAllocator.ServiceAllocate(s); err != nil { nc.unallocatedServices[s.ID] = s return err } if err := a.store.Update(func(tx store.Tx) error { for { err := store.UpdateService(tx, s) if err != nil && err != store.ErrSequenceConflict { return fmt.Errorf("failed updating state in store transaction for service %s: %v", s.ID, err) } if err == store.ErrSequenceConflict { storeService := store.GetService(tx, s.ID) storeService.Endpoint = s.Endpoint s = storeService continue } break } return nil }); err != nil { if err := nc.nwkAllocator.ServiceDeallocate(s); err != nil { log.G(ctx).WithError(err).Errorf("failed rolling back allocation of service %s: %v", s.ID, err) } return err } return nil }
func (a *Allocator) allocateService(ctx context.Context, s *api.Service) error { nc := a.netCtx if s.Spec.Endpoint != nil { // service has user-defined endpoint if s.Endpoint == nil { // service currently has no allocated endpoint, need allocated. s.Endpoint = &api.Endpoint{ Spec: s.Spec.Endpoint.Copy(), } } // The service is trying to expose ports to the external // world. Automatically attach the service to the ingress // network only if it is not already done. if isIngressNetworkNeeded(s) { var found bool for _, vip := range s.Endpoint.VirtualIPs { if vip.NetworkID == nc.ingressNetwork.ID { found = true break } } if !found { s.Endpoint.VirtualIPs = append(s.Endpoint.VirtualIPs, &api.Endpoint_VirtualIP{NetworkID: nc.ingressNetwork.ID}) } } } else if s.Endpoint != nil { // service has no user-defined endpoints while has already allocated network resources, // need deallocated. if err := nc.nwkAllocator.ServiceDeallocate(s); err != nil { return err } } if err := nc.nwkAllocator.ServiceAllocate(s); err != nil { nc.unallocatedServices[s.ID] = s return err } // If the service doesn't expose ports any more and if we have // any lingering virtual IP references for ingress network // clean them up here. if !isIngressNetworkNeeded(s) { if s.Endpoint != nil { for i, vip := range s.Endpoint.VirtualIPs { if vip.NetworkID == nc.ingressNetwork.ID { n := len(s.Endpoint.VirtualIPs) s.Endpoint.VirtualIPs[i], s.Endpoint.VirtualIPs[n-1] = s.Endpoint.VirtualIPs[n-1], nil s.Endpoint.VirtualIPs = s.Endpoint.VirtualIPs[:n-1] break } } } } return nil }
// ServiceAllocate allocates all the network resources such as virtual // IP and ports needed by the service. func (na *NetworkAllocator) ServiceAllocate(s *api.Service) (err error) { if err = na.portAllocator.serviceAllocatePorts(s); err != nil { return } defer func() { if err != nil { na.ServiceDeallocate(s) } }() // If ResolutionMode is DNSRR do not try allocating VIPs. if s.Spec.Endpoint != nil && s.Spec.Endpoint.Mode == api.ResolutionModeDNSRoundRobin { return } if s.Endpoint == nil { s.Endpoint = &api.Endpoint{ Spec: s.Spec.Endpoint.Copy(), } } // First allocate VIPs for all the pre-populated endpoint attachments for _, eAttach := range s.Endpoint.VirtualIPs { if err = na.allocateVIP(eAttach); err != nil { return } } outer: for _, nAttach := range s.Spec.Networks { for _, vip := range s.Endpoint.VirtualIPs { if vip.NetworkID == nAttach.Target { continue outer } } vip := &api.Endpoint_VirtualIP{NetworkID: nAttach.Target} if err = na.allocateVIP(vip); err != nil { return } s.Endpoint.VirtualIPs = append(s.Endpoint.VirtualIPs, vip) } s.Endpoint.Spec = s.Spec.Endpoint.Copy() na.services[s.ID] = struct{}{} return }
func (pa *portAllocator) serviceAllocatePorts(s *api.Service) (err error) { if s.Spec.Endpoint == nil { return nil } // We might have previous allocations which we want to stick // to if possible. So instead of strictly going by port // configs in the Spec reconcile the list of port configs from // both the Spec and runtime state. portConfigs := reconcilePortConfigs(s) // Port configuration might have changed. Cleanup all old allocations first. pa.serviceDeallocatePorts(s) defer func() { if err != nil { // Free all the ports allocated so far which // should be present in s.Endpoints.ExposedPorts pa.serviceDeallocatePorts(s) } }() for _, portConfig := range portConfigs { // Make a copy of port config to create runtime state portState := portConfig.Copy() // Do an actual allocation only if the PublishMode is Ingress if portConfig.PublishMode == api.PublishModeIngress { if err = pa.portSpaces[portState.Protocol].allocate(portState); err != nil { return } } if s.Endpoint == nil { s.Endpoint = &api.Endpoint{} } s.Endpoint.Ports = append(s.Endpoint.Ports, portState) } return nil }
// This function prepares the service object for being updated when the change regards // the published ports in host mode: It resets the runtime state ports (s.Endpoint.Ports) // to the current ingress mode runtime state ports plus the newly configured publish mode ports, // so that the service allocation invoked on this new service object will trigger the deallocation // of any old publish mode port and allocation of any new one. func updatePortsInHostPublishMode(s *api.Service) { if s.Endpoint != nil { var portConfigs []*api.PortConfig for _, portConfig := range s.Endpoint.Ports { if portConfig.PublishMode == api.PublishModeIngress { portConfigs = append(portConfigs, portConfig) } } s.Endpoint.Ports = portConfigs } if s.Spec.Endpoint != nil { if s.Endpoint == nil { s.Endpoint = &api.Endpoint{} } for _, portConfig := range s.Spec.Endpoint.Ports { if portConfig.PublishMode == api.PublishModeIngress { continue } s.Endpoint.Ports = append(s.Endpoint.Ports, portConfig.Copy()) } s.Endpoint.Spec = s.Spec.Endpoint.Copy() } }
// ServiceAllocate allocates all the network resources such as virtual // IP and ports needed by the service. func (na *NetworkAllocator) ServiceAllocate(s *api.Service) (err error) { if err = na.portAllocator.serviceAllocatePorts(s); err != nil { return } defer func() { if err != nil { na.ServiceDeallocate(s) } }() if s.Endpoint == nil { s.Endpoint = &api.Endpoint{} } s.Endpoint.Spec = s.Spec.Endpoint.Copy() // If ResolutionMode is DNSRR do not try allocating VIPs, but // free any VIP from previous state. if s.Spec.Endpoint != nil && s.Spec.Endpoint.Mode == api.ResolutionModeDNSRoundRobin { if s.Endpoint != nil { for _, vip := range s.Endpoint.VirtualIPs { if err := na.deallocateVIP(vip); err != nil { // don't bail here, deallocate as many as possible. log.L.WithError(err). WithField("vip.network", vip.NetworkID). WithField("vip.addr", vip.Addr).Error("error deallocating vip") } } s.Endpoint.VirtualIPs = nil } delete(na.services, s.ID) return } // First allocate VIPs for all the pre-populated endpoint attachments for _, eAttach := range s.Endpoint.VirtualIPs { if err = na.allocateVIP(eAttach); err != nil { return } } // Always prefer NetworkAttachmentConfig in the TaskSpec specNetworks := s.Spec.Task.Networks if len(specNetworks) == 0 && s != nil && len(s.Spec.Networks) != 0 { specNetworks = s.Spec.Networks } outer: for _, nAttach := range specNetworks { for _, vip := range s.Endpoint.VirtualIPs { if vip.NetworkID == nAttach.Target { continue outer } } vip := &api.Endpoint_VirtualIP{NetworkID: nAttach.Target} if err = na.allocateVIP(vip); err != nil { return } s.Endpoint.VirtualIPs = append(s.Endpoint.VirtualIPs, vip) } na.services[s.ID] = struct{}{} return }
func (a *Allocator) allocateService(ctx context.Context, s *api.Service) error { nc := a.netCtx if s.Spec.Endpoint != nil { // service has user-defined endpoint if s.Endpoint == nil { // service currently has no allocated endpoint, need allocated. s.Endpoint = &api.Endpoint{ Spec: s.Spec.Endpoint.Copy(), } } // The service is trying to expose ports to the external // world. Automatically attach the service to the ingress // network only if it is not already done. if len(s.Spec.Endpoint.Ports) != 0 { var found bool for _, vip := range s.Endpoint.VirtualIPs { if vip.NetworkID == nc.ingressNetwork.ID { found = true break } } if !found { s.Endpoint.VirtualIPs = append(s.Endpoint.VirtualIPs, &api.Endpoint_VirtualIP{NetworkID: nc.ingressNetwork.ID}) } } } else if s.Endpoint != nil { // service has no user-defined endpoints while has already allocated network resources, // need deallocated. if err := nc.nwkAllocator.ServiceDeallocate(s); err != nil { return err } } if err := nc.nwkAllocator.ServiceAllocate(s); err != nil { nc.unallocatedServices[s.ID] = s return err } // If the service doesn't expose ports any more and if we have // any lingering virtual IP references for ingress network // clean them up here. if s.Spec.Endpoint == nil || len(s.Spec.Endpoint.Ports) == 0 { if s.Endpoint != nil { for i, vip := range s.Endpoint.VirtualIPs { if vip.NetworkID == nc.ingressNetwork.ID { n := len(s.Endpoint.VirtualIPs) s.Endpoint.VirtualIPs[i], s.Endpoint.VirtualIPs[n-1] = s.Endpoint.VirtualIPs[n-1], nil s.Endpoint.VirtualIPs = s.Endpoint.VirtualIPs[:n-1] break } } } } if err := a.store.Update(func(tx store.Tx) error { for { err := store.UpdateService(tx, s) if err != nil && err != store.ErrSequenceConflict { return fmt.Errorf("failed updating state in store transaction for service %s: %v", s.ID, err) } if err == store.ErrSequenceConflict { storeService := store.GetService(tx, s.ID) storeService.Endpoint = s.Endpoint s = storeService continue } break } return nil }); err != nil { if err := nc.nwkAllocator.ServiceDeallocate(s); err != nil { log.G(ctx).WithError(err).Errorf("failed rolling back allocation of service %s: %v", s.ID, err) } return err } return nil }