Пример #1
0
// addCharmViaAPI calls the appropriate client API calls to add the
// given charm URL to state. Also displays the charm URL of the added
// charm on stdout.
func addCharmViaAPI(client *api.Client, curl *charm.URL, repo charm.Repository) (*charm.URL, error) {
	if curl.Revision < 0 {
		latest, err := charm.Latest(repo, curl)
		if err != nil {
			log.Info("Error find latest version for: %v", curl.String(), err)
			return nil, err
		}
		curl = curl.WithRevision(latest)
	}
	switch curl.Schema {
	case "local":
		ch, err := repo.Get(curl)
		if err != nil {
			return nil, err
		}
		stateCurl, err := client.AddLocalCharm(curl, ch)
		if err != nil {
			return nil, err
		}
		curl = stateCurl
	case "cs":
		err := client.AddCharm(curl)
		if err != nil {
			return nil, err
		}
	default:
		return nil, fmt.Errorf("unsupported charm URL schema: %q", curl.Schema)
	}
	log.Info("Added charm %q to the environment.", curl)
	return curl, nil
}
Пример #2
0
func (self *EndpointServiceInstance) HttpGet() (*rs.HttpResponse, error) {
	service := self.getService()

	log.Info("CF instance GET request: %v", self.Id)

	bundleType, instance := service.getInstance(self.Id)
	if instance == nil || bundleType == nil {
		return nil, rs.ErrNotFound()
	}

	state, err := instance.GetState()
	if err != nil {
		log.Warn("Error while waiting for instance to become ready", err)
		return nil, err
	}

	ready := false
	if state == nil {
		log.Warn("Instance not yet created")
	} else {
		status := state.Status

		if status == "started" {
			ready = true
		} else if status == "pending" {
			ready = false
		} else {
			log.Warn("Unknown instance status: %v", status)
		}
	}

	response := &CfCreateInstanceResponse{}
	// TODO: We need a dashboard URL - maybe a Juju GUI?
	response.DashboardUrl = "http://localhost:8080"
	var cfState string
	if ready {
		cfState = CF_STATE_SUCCEEDED
	} else {
		cfState = CF_STATE_IN_PROGRESS
	}
	response.State = cfState
	response.LastOperation = &CfOperation{}
	response.LastOperation.State = cfState

	log.Info("Sending response to CF service get", log.AsJson(response))

	httpResponse := &rs.HttpResponse{Status: http.StatusOK}
	httpResponse.Content = response
	return httpResponse, nil
}
Пример #3
0
func (self *EtcdRouterRegistry) listSubkeys(key string) ([]string, error) {
	response, err := self.client.Get(key, false, false)
	if err != nil {
		etcdError, ok := err.(*etcd.EtcdError)
		if ok && etcdError.ErrorCode == etcdErrorKeyNotFound {
			log.Debug("Etcd key not found: %v", key)
			return []string{}, nil
		}

		log.Warn("Error reading key from etcd: %v", key, err)
		return nil, err
	}

	if response == nil || response.Node == nil || response.Node.Nodes == nil {
		log.Info("No children for key from etcd: %v", key)
		return []string{}, nil
	}

	names := []string{}
	for _, node := range response.Node.Nodes {
		nodeKey := node.Key
		if !strings.HasPrefix(nodeKey, key) {
			return nil, fmt.Errorf("Key without expected prefix: %v vs %v", nodeKey, key)
		}
		suffix := nodeKey[len(key):]
		names = append(names, suffix)
	}
	return names, nil
}
Пример #4
0
func getCharmInfo(client *api.Client, charmName string, localRepoPath string, defaultSeries string) (*api.CharmInfo, error) {
	curl, err := resolveCharmURL(client, charmName, defaultSeries)
	if err != nil {
		return nil, err
	}

	repo, err := charm.InferRepository(curl.Reference, localRepoPath)
	if err != nil {
		return nil, err
	}

	//	repo = config.SpecializeCharmRepo(repo, defaultSeries)

	curl, err = addCharmViaAPI(client, curl, repo)
	if err != nil {
		return nil, err
	}

	charmInfo, err := client.CharmInfo(curl.String())
	if err != nil {
		log.Info("Error getting charm info for: %v", curl.String(), err)
		return nil, err
	}
	return charmInfo, nil
}
Пример #5
0
func (self *EtcdRouterRegistry) read(key string) (*etcdRouterData, error) {
	response, err := self.client.Get(key, false, false)
	if err != nil {
		etcdError, ok := err.(*etcd.EtcdError)
		if ok && etcdError.ErrorCode == etcdErrorKeyNotFound {
			log.Debug("Etcd key not found: %v", key)
			return nil, nil
		}

		log.Warn("Error reading key from etcd: %v", key, err)
		return nil, err
	}

	node := response.Node
	if node == nil || node.Value == "" {
		log.Info("No contents for key from etcd: %v", key)
		return nil, nil
	}

	decoded := &etcdRouterData{}
	err = json.Unmarshal([]byte(node.Value), decoded)
	if err != nil {
		log.Warn("Error parsing value from etcd: %v", node.Value, err)
		return nil, err
	}

	return decoded, nil
}
Пример #6
0
func (self *EndpointServiceInstance) HttpDelete(httpRequest *http.Request) (*CfDeleteInstanceResponse, error) {
	service := self.getService()

	queryValues := httpRequest.URL.Query()
	serviceId := queryValues.Get("service_id")
	//	planId := queryValues.Get("plan_id")

	if serviceId != service.CfServiceId {
		log.Warn("Service mismatch: %v vs %v", serviceId, service.CfServiceId)
		return nil, rs.ErrNotFound()
	}

	log.Info("Deleting item %v %v", serviceId, self.Id)

	bundletype, instance := service.getInstance(self.getInstanceId())
	if instance == nil || bundletype == nil {
		return nil, rs.ErrNotFound()
	}

	err := instance.Delete()
	if err != nil {
		return nil, err
	}

	// TODO: Wait for deletion?

	response := &CfDeleteInstanceResponse{}
	return response, nil
}
Пример #7
0
func (self *EndpointInstanceScaling) HttpPut(policyUpdate *model.ScalingPolicy) (*model.Scaling, error) {
	instance := self.Parent.getInstance()

	log.Info("Policy update: %v", policyUpdate)

	exists, err := instance.Exists()
	if err != nil {
		return nil, err
	}
	if !exists {
		return nil, rs.ErrNotFound()
	}

	if policyUpdate != nil {
		_, err := instance.UpdateScalingPolicy(policyUpdate)
		if err != nil {
			log.Warn("Error updating scaling policy", err)
			return nil, err
		}
	}

	results, err := instance.RunScaling(true)
	if err != nil {
		return nil, err
	}
	return results, nil
}
Пример #8
0
// Delete any relation properties relating to the specified unit; that unit is going away.
func (self *Instance) DeleteRelationInfo(unitId string, relationId string) error {
	jujuClient := self.GetJujuClient()

	serviceId := self.primaryServiceId

	prefix := ANNOTATION_PREFIX_RELATIONINFO + unitId + "_" + relationId + "_"

	annotations, err := jujuClient.GetServiceAnnotations(serviceId)
	if err != nil {
		log.Warn("Error getting annotations", err)
		// TODO: Mask error?
		return err
	}

	deleteKeys := []string{}

	for tagName, _ := range annotations {
		if !strings.HasPrefix(tagName, prefix) {
			continue
		}
		deleteKeys = append(deleteKeys, tagName)
	}

	if len(deleteKeys) != 0 {
		log.Info("Deleting annotations on service %v: %v", serviceId, deleteKeys)

		err = jujuClient.DeleteServiceAnnotations(serviceId, deleteKeys)
		if err != nil {
			log.Warn("Error deleting annotations", err)
			return err
		}
	}

	return nil
}
Пример #9
0
func localIP() (net.IP, error) {
	netInterfaces, err := net.Interfaces()
	if err != nil {
		return nil, err
	}
	for _, netInterface := range netInterfaces {
		addresses, err := netInterface.Addrs()
		if err != nil {
			return nil, err
		}
		for _, address := range addresses {
			ipnet, ok := address.(*net.IPNet)
			if !ok {
				continue
			}
			v4 := ipnet.IP.To4()
			if v4 == nil || v4[0] == 127 { // loopback address
				continue
			}
			log.Info("Chose local IP: %v", v4)
			return v4, nil
		}
	}
	return nil, errors.New("Cannot find local IP address")
}
Пример #10
0
func GetOptions() *Options {
	flag.Parse()

	self := &Options{}

	self.AgentConf = *flagAgentConf
	self.ApiPasswordPath = *flagApiPasswordPath

	self.CfTenantId = *flagCfTenantId
	self.ListenAddress = *flagListenAddress

	host, port, err := net.SplitHostPort(self.ListenAddress)
	if err != nil {
		log.Warn("Cannot parse listen address: %v", self.ListenAddress)
		return nil
	}
	var portNum int
	if port == "" {
		portNum = 8080
	} else {
		portNum, err = net.LookupPort("tcp", port)
		if err != nil {
			log.Warn("Cannot resolve port: %v", port)
			return nil
		}
	}

	privateUrl := *flagPrivateUrl
	if privateUrl == "" {
		privateHost := host
		if privateHost == "" {
			ip, err := localIP()
			if err != nil {
				log.Warn("Error finding local IP", err)
				return nil
			}
			privateHost = ip.String()
		}

		privateUrl = fmt.Sprintf("http://%v:%v/xaasprivate", privateHost, portNum)
		log.Info("Chose private url: %v", privateUrl)
	}
	self.PrivateUrl = privateUrl

	authMode := *flagAuth
	authMode = strings.TrimSpace(authMode)
	authMode = strings.ToLower(authMode)
	if authMode == "openstack" {
		keystoneUrl := *flagKeystoneUrl
		self.Authenticator = auth.NewOpenstackMultiAuthenticator(keystoneUrl)
	} else if authMode == "development" {
		self.Authenticator = auth.NewDevelopmentAuthenticator()
	} else {
		log.Warn("Unknown authentication mode: %v", authMode)
		return nil
	}

	return self
}
Пример #11
0
Файл: rpc.go Проект: jxaas/jxaas
// update_relation_properties RPC handler
func (self *EndpointRpcUpdateRelationProperties) HttpPost(huddle *core.Huddle, request *RpcUpdateRelationPropertiesRequest) (*RpcUpdateRelationPropertiesResponse, error) {
	// TODO: Validate that this is coming from one of our machines?

	log.Info("Got RPC request: UpdateRelationProperties: %v", request)

	response := &RpcUpdateRelationPropertiesResponse{}

	// Sanitize
	if request.Properties == nil {
		request.Properties = make(map[string]string)
	}

	localUnit := request.ServiceName
	if localUnit == "" {
		return nil, fmt.Errorf("ServiceName is required")
	}
	tenant, bundleTypeName, instanceId, _, _, err := core.ParseUnit(localUnit)
	if err != nil {
		return nil, err
	}

	bundleType := huddle.System.GetBundleType(bundleTypeName)
	if bundleType == nil {
		return nil, fmt.Errorf("Unknown bundle type: %v", bundleTypeName)
	}

	primaryService := bundleType.PrimaryJujuService()

	//	remoteUnit := request.RemoteName
	//	if remoteUnit == "" {
	//		// We're a bit stuck here.  We do have the relationId and other info,
	//		// we just don't have the remote relation, and we're storing the attributes on the remote relation
	//		// TODO: Infer the remote relation? (-stubclient to -primary)?
	//		log.Warn("No remote unit; can't remove relations")
	//		return response, nil
	//	}
	//
	//	_, _, remoteInstanceId, _, remoteUnitId, err := core.ParseUnit(remoteUnit)
	//	if err != nil {
	//		return nil, err
	//	}

	instance := huddle.NewInstance(tenant, bundleType, instanceId)

	relationId := request.RelationId

	if request.Action == "broken" {
		err = instance.DeleteRelationInfo(primaryService, relationId)
	} else {
		err = instance.SetRelationInfo(primaryService, relationId, request.Properties)
	}

	if err != nil {
		return nil, err
	}

	return response, nil
}
Пример #12
0
func isHuddleReady(huddle *core.Huddle) bool {
	for key, service := range huddle.SystemServices {
		if service.PublicAddress == "" {
			log.Info("Service not ready (no public address): %v", key)
			return false
		}
	}
	return true
}
Пример #13
0
func (self *ServiceHealthCheck) checkService(instance jxaas.Instance, serviceId string, repair bool, dest *model.Health) error {
	client := instance.GetJujuClient()

	command := "service " + self.ServiceName + " status"
	log.Info("Running command on %v: %v", serviceId, command)

	runResults, err := client.Run(serviceId, nil, command, 5*time.Second)
	if err != nil {
		return err
	}

	for _, runResult := range runResults {
		unitId := juju.ParseUnit(runResult.UnitId)

		code := runResult.Code
		stdout := string(runResult.Stdout)
		stderr := string(runResult.Stderr)

		log.Debug("Result: %v %v %v %v", runResult.UnitId, code, stdout, stderr)

		healthy := true
		if !strings.Contains(stdout, "start/running") {
			log.Info("Service %v not running on %v", serviceId, runResult.UnitId)
			healthy = false

			if repair {
				command := "service " + self.ServiceName + " start"
				log.Info("Running command on %v: %v", serviceId, command)

				_, err := client.Run(serviceId, []string{unitId}, command, 5*time.Second)
				if err != nil {
					return err
				}

			}
		}

		dest.Units[unitId] = healthy
	}

	return nil
}
Пример #14
0
func (self *Bundle) ApplyImplicits(templateContext *TemplateContext) {
	for _, v := range self.Services {
		v.applyImplicits(templateContext)
	}

	for _, v := range self.Relations {
		v.applyImplicits(templateContext)
	}

	for k, v := range self.Provides {
		v.applyImplicits(templateContext, k)
	}

	stub, found := self.Services["sc"]
	if found {
		self.configureStubClient(templateContext, stub)
		log.Info("Configured stubclient: %v", stub)
	} else {
		log.Info("sc (stubclient) not found")
	}
}
Пример #15
0
func (self *CharmReader) read(name string) ([]byte, error) {
	inputStream, err := self.byteSource.Open()
	if err != nil {
		return nil, err
	}

	defer func() {
		closeable, ok := inputStream.(io.Closer)
		if ok {
			closeable.Close()
		}
	}()

	size, err := self.byteSource.Size()
	if err != nil {
		return nil, err
	}

	readerAt, ok := inputStream.(io.ReaderAt)
	if !ok {
		return nil, fmt.Errorf("Expected ReaderAt")
	}

	r, err := zip.NewReader(readerAt, size)
	if err != nil {
		return nil, err
	}

	for _, f := range r.File {
		log.Info("File: %v", f.Name)
	}

	for _, f := range r.File {
		if f.Name != name {
			continue
		}

		rc, err := f.Open()
		if err != nil {
			return nil, err
		}
		defer rc.Close()

		data, err := ioutil.ReadAll(rc)
		if err != nil {
			return nil, err
		}

		return data, nil
	}

	return nil, nil
}
Пример #16
0
func (self *Huddle) cleanupOldMachines(state map[string]int, threshold int) (map[string]int, error) {
	status, err := self.JujuClient.GetSystemStatus()
	if err != nil {
		log.Warn("Error getting system status", err)
		return nil, err
	}

	unitsByMachine := map[string]*api.UnitStatus{}

	for _, serviceStatus := range status.Services {
		for _, unitStatus := range serviceStatus.Units {
			machineId := unitStatus.Machine
			unitsByMachine[machineId] = &unitStatus
		}
	}

	idleMachines := map[string]*api.MachineStatus{}
	for machineId, machineStatus := range status.Machines {
		unit := unitsByMachine[machineId]
		if unit != nil {
			continue
		}
		idleMachines[machineId] = &machineStatus
	}

	idleCounts := map[string]int{}
	for machineId, _ := range idleMachines {
		idleCount := state[machineId]
		idleCount++
		idleCounts[machineId] = idleCount
	}

	for machineId, idleCount := range idleCounts {
		if idleCount < threshold {
			continue
		}

		if machineId == "0" {
			// Machine id 0 is special (the system machine); we can't destroy it
			continue
		}

		log.Info("Machine is idle; removing: %v", machineId)
		err = self.JujuClient.DestroyMachine(machineId)
		if err != nil {
			log.Warn("Failed to delete machine %v", machineId, err)
		}
	}

	return idleCounts, nil
}
Пример #17
0
// Sets annotations on the specified instance.
func (self *Instance) setServiceAnnotations(pairs map[string]string) error {
	jujuClient := self.GetJujuClient()
	serviceId := self.primaryServiceId

	log.Info("Setting annotations on service %v: %v", serviceId, pairs)

	err := jujuClient.SetServiceAnnotations(serviceId, pairs)
	if err != nil {
		log.Warn("Error setting annotations", err)
		// TODO: Mask error?
		return err
	}

	return nil
}
Пример #18
0
func (self *baseBundleType) BuildRelationInfo(templateContext *bundle.TemplateContext, bundle *bundle.Bundle, relationKey string) (*model.RelationInfo, error) {
	log.Info("BuildRelationInfo with %v", templateContext)

	// Find the properties the juju charm is exposing
	relationProperties := templateContext.Relations[relationKey]

	// Map those properties using the definition
	provideProperties := map[string]string{}

	if len(bundle.Provides) == 0 {
		// No explicit provides => derive automatically
		for k, v := range relationProperties {
			v = templateContext.GetSpecialProperty(relationKey, k, v)
			provideProperties[k] = v
		}

		// Auto-populate required properties that we generate
		required := []string{"protocol", "port"}
		for _, k := range required {
			v, found := relationProperties[k]
			if !found {
				v = templateContext.GetSpecialProperty(relationKey, k, v)
			}
			provideProperties[k] = v
		}

	} else {
		definition, found := bundle.Provides[relationKey]
		if !found {
			// Explicit provides, but no definition => no relation
			log.Debug("Request for relation, but no definition found: %v", relationKey)
			return nil, nil
		}

		for k, v := range definition.Properties {
			provideProperties[k] = v
		}
	}

	relationInfo := &model.RelationInfo{}
	if templateContext.Proxy != nil {
		relationInfo.PublicAddresses = []string{templateContext.Proxy.Host}
	}
	relationInfo.Properties = provideProperties

	return relationInfo, nil
}
Пример #19
0
func TestBundleStore_Get(t *testing.T) {
	store := NewBundleStore("../templates")
	templateContext := &TemplateContext{}
	tenant := "123"
	serviceType := "mysql"
	name := "test"

	templateContext.NumberUnits = 3
	templateContext.Options = map[string]string{}
	templateContext.Options["performance"] = "high"

	bundle, err := store.GetBundle(templateContext, tenant, serviceType, name)

	if err != nil {
		t.Fatal("Unable to load bundle", err)
	}

	if bundle == nil {
		t.Fatal("Bundle was nil")
	}

	prefix := buildPrefix(tenant, serviceType, name)

	service, found := bundle.Services[prefix+"mysql"]
	if !found {
		log.Info("Services: %v", bundle.Services)

		t.Fatal("mysql service not found")
	}

	if service.NumberUnits != 3 {
		t.Fatal("NumberUnits was not copied")
	}

	if service.Options["performance"] != "high" {
		t.Fatal("Performance option was not copied")
	}
}
Пример #20
0
func NewHuddle(system *System, bundleStore *bundle.BundleStore, jujuApi *juju.Client, privateUrl string) (*Huddle, error) {
	key := "shared"

	huddle := &Huddle{}
	environmentInfo, err := jujuApi.EnvironmentInfo()
	if err != nil {
		log.Warn("Error reading juju environment info", err)
		return nil, err
	}
	if environmentInfo == nil {
		return nil, fmt.Errorf("No juju environment info found")
	}

	huddle.environmentProviderType = environmentInfo.ProviderType
	if huddle.environmentProviderType == "" {
		return nil, fmt.Errorf("Juju environment info invalid: no ProviderType")
	}
	log.Info("Juju environment ProviderType is '%v'", huddle.environmentProviderType)

	systemBundle, err := bundleStore.GetSystemBundle(key)
	if err != nil {
		log.Warn("Error loading system bundle: %v", key, err)
		return nil, err
	}

	if systemBundle == nil {
		log.Warn("Cannot load system bundle: %v", key, err)
		return nil, nil
	}

	info, err := systemBundle.Deploy("jx-", jujuApi)
	if err != nil {
		log.Warn("Error deploying system bundle", err)
		return nil, err
	}

	huddle.PrivateUrl = privateUrl
	huddle.SystemServices = map[string]*SystemService{}
	huddle.assignedPublicPorts = map[string]int{}

	for key, service := range info.Services {
		systemService := &SystemService{}
		systemService.JujuName = "jx-" + key
		systemService.Key = key

		status := service.Status
		if status != nil {
			for _, unit := range status.Units {
				if unit.PublicAddress != "" {
					systemService.PublicAddress = unit.PublicAddress
				}

				externalAddress := ""
				if unit.Machine != "" {
					externalAddress, err = jujuApi.PublicAddress(unit.Machine)
					if err != nil {
						log.Warn("Error getting public address for machine", err)
						return nil, err
					} else {
						if huddle.IsAmazon() {
							// Work around a problem where we sometimes get an address that is ip-X-X-X-X.ece2.internal
							// I think this is a Juju bug (?)
							if strings.HasSuffix(externalAddress, ".ec2.internal") {
								log.Warn("Juju gave invalid PublicAddress: %v", externalAddress)
								externalAddress = systemService.PublicAddress
							}

							// Amazon has a special DNS name: ec2-54-172-123-123.compute-1.amazonaws.com
							// Externally that resolves to 54.172.123.123 (i.e. the value embedded in the name)
							// Internally (inside EC2) that resolves to the internal IP (172.16.x.x)
							// We don't want that internal resolution to happen here (this is an _external_ IP)
							// But we may be within EC2, so we can't simply resolve the name
							if strings.HasPrefix(externalAddress, "ec2-") && strings.HasSuffix(externalAddress, ".compute-1.amazonaws.com") {
								ipString := externalAddress[4:]
								firstDot := strings.IndexRune(ipString, '.')
								ipString = ipString[:firstDot]

								ipString = strings.Replace(ipString, "-", ".", -1)

								log.Info("Replaced EC2 switching-address '%v' with IP '%v'", externalAddress, ipString)
								externalAddress = ipString
							}
						}

						if externalAddress != "" {
							log.Info("Chose public address for machine: '%v'", externalAddress)
						} else {
							log.Warn("Got empty public address for machine: %v", unit.Machine)
						}
					}
				}

				if externalAddress == "" {
					log.Warn("Unable to get external address for machine %v, falling back to public address %v", unit.Machine, systemService.PublicAddress)
					externalAddress = systemService.PublicAddress
				}
				systemService.ExternalAddress = externalAddress
			}
		}

		huddle.SystemServices[key] = systemService
	}

	huddle.JujuClient = jujuApi
	huddle.System = system
	// TODO: Wait until initialized or offer a separate 'bootstrap' command

	{
		check := &HealthCheckAllInstances{}
		check.huddle = huddle
		check.repair = true
		system.Scheduler.AddTask(check, time.Minute*1)
	}

	{
		scaling := &AutoScaleAllInstances{}
		scaling.huddle = huddle
		system.Scheduler.AddTask(scaling, time.Minute*1)
	}

	{
		task := &CleanupOldMachines{}
		task.huddle = huddle
		system.Scheduler.AddTask(task, time.Minute*5)
	}

	return huddle, nil
}
Пример #21
0
// Runs a scaling query and/or change on the instance
func (self *Instance) RunScaling(changeScale bool) (*model.Scaling, error) {
	health := &model.Scaling{}

	instanceState, err := self.GetState()
	if err != nil {
		log.Warn("Error getting instance state", err)
		return nil, err
	}

	assert.That(instanceState.NumberUnits != nil)
	scaleCurrent := *instanceState.NumberUnits
	health.ScaleCurrent = scaleCurrent
	health.ScaleTarget = scaleCurrent

	policy, err := self.getScalingPolicy()
	if err != nil {
		log.Warn("Error fetching scaling policy", err)
		return nil, err
	}

	health.Policy = *policy

	var scaleTarget int

	if policy.MetricName != nil {
		// TODO: Filter by time window
		metricData, err := self.GetMetricValues(*policy.MetricName)
		if err != nil {
			log.Warn("Error retrieving metrics for scaling", err)
			return nil, err
		}

		window := 300
		if policy.Window != nil {
			window = *policy.Window
		}
		duration := time.Duration(-window) * time.Second

		now := time.Now()
		maxTime := now.Unix()
		minTime := now.Add(duration).Unix()

		matches := &model.MetricDataset{}
		for _, point := range metricData.Points {
			t := point.T

			if t < minTime {
				continue
			}

			if t > maxTime {
				continue
			}

			matches.Points = append(matches.Points, point)
		}

		matches.SortPointsByTime()

		lastTime := minTime
		var total float64
		for _, point := range matches.Points {
			t := point.T

			assert.That(t >= lastTime)

			total += float64(float32(t-lastTime) * point.V)

			lastTime = t
		}

		metricCurrent := float32(total / float64(lastTime-minTime))
		log.Info("Average of metric: %v", metricCurrent)

		health.MetricCurrent = metricCurrent

		// TODO: Smart 'target-based' scaling
		scaleDelta := 0
		if policy.MetricMin != nil && metricCurrent < *policy.MetricMin {
			scaleDelta = -1
		} else if policy.MetricMax != nil && metricCurrent > *policy.MetricMax {
			scaleDelta = +1
		}

		scaleTarget = scaleCurrent + scaleDelta
	} else {
		scaleTarget = scaleCurrent
	}

	if policy.ScaleMax != nil && scaleTarget > *policy.ScaleMax {
		scaleTarget = *policy.ScaleMax
	} else if policy.ScaleMin != nil && scaleTarget < *policy.ScaleMin {
		scaleTarget = *policy.ScaleMin
	}

	health.ScaleTarget = scaleTarget

	if changeScale && health.ScaleTarget != scaleCurrent {
		log.Info("Changing scale from %v to %v for %v", scaleCurrent, health.ScaleTarget, self)

		rescale := &model.Instance{}
		rescale.NumberUnits = new(int)
		*rescale.NumberUnits = health.ScaleTarget

		err := self.Configure(rescale)
		if err != nil {
			log.Warn("Error changing scale", err)
			return nil, err
		}
	}

	if math.IsNaN(float64(health.MetricCurrent)) {
		// Avoid error when golang tries to convert NaN to JSON (!)
		health.MetricCurrent = 0
	}

	return health, nil
}
Пример #22
0
// Assigns a public port to the serviceId
func (self *Huddle) assignPublicPort(serviceId string) (int, bool, error) {
	self.mutex.Lock()
	defer self.mutex.Unlock()

	var port int

	port, found := self.assignedPublicPorts[serviceId]
	if found {
		return port, false, nil
	}

	// TODO: Filter?
	prefix := ""
	statuses, err := self.JujuClient.GetServiceStatusList(prefix)
	if err != nil {
		return 0, false, err
	}

	publicPorts := []int{}

	for _, publicPort := range self.assignedPublicPorts {
		publicPorts = append(publicPorts, publicPort)
	}

	for key, _ := range statuses {
		var publicPort int

		publicPort, found := self.assignedPublicPorts[key]
		if found {
			assert.That(contains(publicPorts, publicPort))
			continue
		}

		log.Debug("Looking for public port annotation on: %v", key)

		annotations, err := self.JujuClient.GetServiceAnnotations(key)
		if err != nil {
			return 0, false, err
		}

		publicPortString := annotations[ANNOTATION_KEY_PUBLIC_PORT]
		publicPortString = strings.TrimSpace(publicPortString)
		if publicPortString == "" {
			continue
		}
		publicPort, err = strconv.Atoi(publicPortString)
		if err != nil {
			log.Warn("Error parsing public port on %v: %v", key, publicPortString, err)
		}
		self.assignedPublicPorts[key] = publicPort

		publicPorts = append(publicPorts, publicPort)
	}

	// This approach breaks down if the ports are densely assigned
	if len(publicPorts) > 9000 {
		return 0, false, fmt.Errorf("Too many ports already assigned")
	}

	for {
		port = 10000 + rand.Intn(10000)
		if contains(publicPorts, port) {
			continue
		}

		log.Debug("Public ports already assigned: %v", publicPorts)
		log.Info("Assigned port: %v", port)
		break
	}

	// We can't set the port yet; the service likely doesn't yet exist
	//	err = self.Instance.setPublicPort(port)
	//	if err != nil {
	//		return 0, err
	//	}

	// Instead we set the port in the map; this map is how we avoid double allocations before
	// we've created the service
	self.assignedPublicPorts[serviceId] = port

	return port, true, nil
}
Пример #23
0
func (self *Instance) buildCurrentTemplateContext(state *instanceState, cloudfoundry bool) (*bundle.TemplateContext, error) {
	var err error

	if state == nil {
		state, err = self.getState0()
		if err != nil {
			log.Warn("Error getting instance state", err)
			return nil, err
		}
	}

	context := self.buildSkeletonTemplateContext()

	// TODO: Need to determine current # of units
	context.NumberUnits = 1

	if state != nil && state.Model != nil {
		context.Options = state.Model.Options
	} else {
		context.Options = map[string]string{}
	}

	publicPortAssigner := &InstancePublicPortAssigner{}
	publicPortAssigner.Instance = self
	context.PublicPortAssigner = publicPortAssigner

	// Populate relation info
	if state != nil {
		context.Relations = state.Relations
	}

	// Populate proxy
	// TODO: Skip proxy host on EC2?
	useProxyHost := true

	if state != nil {
		systemProperties := state.SystemProperties

		if useProxyHost && systemProperties[SYSTEM_KEY_PUBLIC_PORT] != "" {
			publicPortString := systemProperties[SYSTEM_KEY_PUBLIC_PORT]
			publicPort, err := strconv.Atoi(publicPortString)
			if err != nil {
				log.Warn("Error parsing public port: %v", publicPortString, err)
				return nil, err
			}

			proxyHost, err := self.huddle.getProxyHost(false)
			if err != nil {
				log.Warn("Error fetching proxy host", err)
				return nil, err
			}

			if cloudfoundry && self.huddle.IsAmazon() {
				// CloudFoundry prevents apps from accessing private CIDRs (10.x.x.x, 172.16.x.x etc)
				// This would be OK, because the hostname on EC2 is a DNS name assigned by AWS to the instance
				// .. except that within AWS, this hostname resolves to an internal IP.
				// For this special case, we force the extenal IP
				log.Info("Using external IP for AWS & CloudFoundry")
				proxyHost, err = self.huddle.getProxyHost(true)
				if err != nil {
					log.Warn("Error fetching (external) proxy host", err)
					return nil, err
				}
			}

			context.Proxy = &bundle.ProxySettings{}
			context.Proxy.Host = proxyHost
			context.Proxy.Port = publicPort
		}
	}

	return context, nil
}
Пример #24
0
// Runs a health check on the instance
func (self *Instance) RunHealthCheck(repair bool) (*model.Health, error) {
	jujuClient := self.GetJujuClient()

	state, err := self.getState0()
	if err != nil {
		return nil, err
	}

	if state == nil {
		return nil, rs.ErrNotFound()
	}

	if state.Model == nil {
		log.Debug("No model for %v", self)
		return nil, rs.ErrNotFound()
	}

	if state.Model.Status != "started" {
		log.Info("Skipping health check on not-yet started instance (state %v): %s", state.Model.Status, self)
		return nil, nil
	}

	services, err := jujuClient.GetServiceStatusList(self.jujuPrefix)
	if err != nil {
		return nil, err
	}

	if services == nil || len(services) == 0 {
		return nil, rs.ErrNotFound()
	}

	bundle, err := self.getCurrentBundle(state)
	if err != nil {
		return nil, err
	}

	health := &model.Health{}
	health.Units = map[string]bool{}

	healthChecks, err := self.bundleType.GetHealthChecks(bundle)
	if err != nil {
		return nil, err
	}

	// TODO: We can't "juju run" on subordinate charms
	//		charm := self.huddle.getCharmInfo(service.Charm)
	//
	//		if charm.Subordinate {
	//			continue
	//		}

	for healthCheckId, healthCheck := range healthChecks {
		result, err := healthCheck.Run(self, services, repair)

		if err != nil {
			log.Info("Health check %v failed", healthCheckId, err)
			return nil, err
		}

		for k, healthy := range result.Units {
			overall, exists := health.Units[k]
			if !exists {
				overall = true
			}
			health.Units[k] = overall && healthy
		}
	}

	return health, nil
}
Пример #25
0
func (self *EndpointServiceInstance) HttpPut(request *CfCreateInstanceRequest) (*rs.HttpResponse, error) {
	service := self.getService()

	log.Info("CF instance put request: %v", request)

	planId := request.PlanId
	cfServiceId := request.ServiceId

	if cfServiceId != service.CfServiceId {
		log.Warn("Service mismatch: %v vs %v", cfServiceId, service.CfServiceId)
		return nil, rs.ErrNotFound()
	}

	bundleType, instance := service.getInstance(self.Id)
	if instance == nil || bundleType == nil {
		return nil, rs.ErrNotFound()
	}

	cfPlans, err := bundleType.GetCloudFoundryPlans()
	if err != nil {
		log.Warn("Error retrieving CloudFoundry plans for bundle %v", bundleType, err)
		return nil, err
	}

	var foundPlan *bundle.CloudFoundryPlan
	for _, cfPlan := range cfPlans {
		cfPlanId := service.CfServiceId + "::" + cfPlan.Key
		if cfPlanId == planId {
			assert.That(foundPlan == nil)
			foundPlan = cfPlan
		}
	}

	if foundPlan == nil {
		log.Warn("Plan not found %v", planId)
		return nil, rs.ErrNotFound()
	}

	log.Debug("Found CF plan: %v", foundPlan)

	configureRequest := &model.Instance{}
	configureRequest.Options = foundPlan.Options

	err = instance.Configure(configureRequest)
	if err != nil {
		return nil, err
	}

	response := &CfCreateInstanceResponse{}
	// TODO: We need a dashboard URL - maybe a Juju GUI?
	response.DashboardUrl = "http://localhost:8080"
	response.State = CF_STATE_IN_PROGRESS
	response.LastOperation = &CfOperation{}
	response.LastOperation.State = CF_STATE_IN_PROGRESS

	log.Info("Sending response to CF service create", log.AsJson(response))

	httpResponse := &rs.HttpResponse{Status: http.StatusAccepted}
	httpResponse.Content = response
	return httpResponse, nil
}
Пример #26
0
func (self *RestEndpointHandler) httpHandler(res http.ResponseWriter, req *http.Request) {
	requestUrl := req.URL
	requestMethod := req.Method

	log.Debug("%v %v", requestMethod, requestUrl)

	endpoint, err := self.resolveEndpoint(req)

	if endpoint == nil && err == nil {
		err = HttpError(http.StatusNotFound)
	}

	var method reflect.Value

	if err == nil {
		httpMethod := req.Method
		methodName := "Http" + httpMethod[0:1] + strings.ToLower(httpMethod[1:])

		method = endpoint.MethodByName(methodName)
		if !method.IsValid() {
			log.Debug("Method not found: %v on %v", methodName, endpoint.Type())

			err = HttpError(http.StatusNotFound)
		}
	}

	var args []reflect.Value
	if err == nil {
		args, err = self.buildArgs(req, &method)
	}

	var val reflect.Value

	if err == nil {
		var out []reflect.Value
		out = method.Call(args)
		//		fmt.Fprintf(w, "Returned %v", out)

		val, err = parseReturn(out)
	}

	if err == nil {
		if val.IsNil() {
			err = HttpError(http.StatusNotFound)
		}
	}

	var response *HttpResponse
	var mbw MessageBodyWriter

	if err == nil {
		response, err = self.makeResponse(val)
	}
	if err == nil {
		assert.That(response != nil)

		if response.Headers == nil {
			response.Headers = make(map[string]string)
		}

		if response.Content == nil {
			mbw = &NoResponseMessageBodyWriter{}
		} else {
			contentType := response.Headers["content-type"]
			if contentType == "" {
				contentType = "application/json; charset=utf-8"
				response.Headers["content-type"] = contentType
			}

			var mediaType *MediaType
			if contentType != "" {
				mediaType, err = ParseMediaType(contentType)
			}

			if err == nil {
				assert.That(mediaType != nil)

				mbw = self.server.findMessageBodyWriter(response.Content, req, mediaType)
				if mbw == nil {
					log.Warn("Unable to find media type: %v", contentType)
					err = HttpError(http.StatusUnsupportedMediaType)
				}
			}
		}
	}

	if err == nil {
		assert.That(response != nil)
		assert.That(mbw != nil)

		log.Info("%v %v %v", response.Status, requestMethod, requestUrl)

		if response.Headers != nil {
			for name, value := range response.Headers {
				res.Header().Set(name, value)
			}
		}

		res.WriteHeader(response.Status)

		err = mbw.Write(response.Content, reflect.TypeOf(response.Content), req, res)
		if err != nil {
			log.Warn("Error while writing message body", err)
		}
	} else {
		httpError, ok := err.(*HttpErrorObject)
		if !ok {
			log.Warn("Internal error serving request", err)
			httpError = HttpError(http.StatusInternalServerError)
		}

		status := httpError.Status
		message := httpError.Message
		if message == "" {
			message = http.StatusText(status)
			if message == "" {
				message = "Error"
			}
		}

		for k, v := range httpError.Headers {
			res.Header().Add(k, v)
		}

		log.Info("%v %v %v", status, requestMethod, requestUrl)

		http.Error(res, message, status)
	}
}
Пример #27
0
// Returns the current state of the instance
func (self *Instance) getState0() (*instanceState, error) {
	jujuClient := self.GetJujuClient()

	primaryServiceId := self.primaryServiceId
	status, err := jujuClient.GetServiceStatus(primaryServiceId)

	// TODO: check err?

	jujuService, err := jujuClient.FindService(primaryServiceId)
	if err != nil {
		return nil, err
	}

	if status == nil {
		log.Warn("No state found for %v", primaryServiceId)
		return nil, nil
	}

	log.Debug("Service state: %v", status)

	state := &instanceState{}
	state.Model = model.MapToInstance(self.instanceId, status, jujuService)

	for k, v := range self.bundleType.GetDefaultOptions() {
		option, found := state.Model.OptionDescriptions[k]
		if !found {
			log.Warn("Option not found in OptionDescriptions %v in %v", k, state.Model.OptionDescriptions)
			continue
		}
		option.Default = v
		state.Model.OptionDescriptions[k] = option
	}

	state.Units = map[string]map[string]api.UnitStatus{}

	state.Units[primaryServiceId] = status.Units

	state.PublicAddresses = []string{}
	for _, unitStatus := range status.Units {
		if unitStatus.PublicAddress == "" {
			continue
		}
		state.PublicAddresses = append(state.PublicAddresses, unitStatus.PublicAddress)
	}

	serviceKeys, err := self.getBundleKeys()
	if err != nil {
		return nil, err
	}

	if serviceKeys == nil {
		return nil, rs.ErrNotFound()
	}

	// TODO: This is pretty expensive... we could just check to see if properties have been set
	for serviceId, _ := range serviceKeys {
		if serviceId == primaryServiceId {
			continue
		}

		status, err := jujuClient.GetServiceStatus(serviceId)
		if err != nil {
			log.Warn("Error while fetching status of service: %v", serviceId, err)
			state.Model.Status = "pending"
		} else if status == nil {
			log.Warn("No status for service: %v", serviceId)
			state.Model.Status = "pending"
		} else {
			log.Info("Got state of secondary service: %v => %v", serviceId, status)
			for _, unitStatus := range status.Units {
				model.MergeInstanceStatus(state.Model, &unitStatus)
			}
		}

		if status != nil {
			state.Units[serviceId] = status.Units
		}
	}

	// TODO: This is a bit of a hack also.  How should we wait for properties to be set?
	annotations, err := jujuClient.GetServiceAnnotations(primaryServiceId)
	if err != nil {
		log.Warn("Error getting annotations", err)
		// TODO: Mask error?
		return nil, err
	}

	log.Info("Annotations on %v: %v", primaryServiceId, annotations)

	state.Model.Options = map[string]string{}

	state.SystemProperties = map[string]string{}
	state.RelationMetadata = map[string]string{}

	relationList := []relationProperty{}

	for tagName, v := range annotations {
		if strings.HasPrefix(tagName, ANNOTATION_PREFIX_INSTANCECONFIG) {
			key := tagName[len(ANNOTATION_PREFIX_INSTANCECONFIG):]
			state.Model.Options[key] = v
			continue
		}

		if strings.HasPrefix(tagName, ANNOTATION_PREFIX_SYSTEM) {
			key := tagName[len(ANNOTATION_PREFIX_SYSTEM):]
			state.SystemProperties[key] = v
			continue
		}

		if strings.HasPrefix(tagName, ANNOTATION_PREFIX_RELATIONINFO) {
			suffix := tagName[len(ANNOTATION_PREFIX_RELATIONINFO):]
			tokens := strings.SplitN(suffix, "_", 3)
			if len(tokens) < 3 {
				log.Debug("Ignoring unparseable tag: %v", tagName)
				continue
			}

			unitId := tokens[0]
			relationId := tokens[1]
			key := tokens[2]
			if key[0] != '_' {
				state.RelationMetadata[key] = v
				continue
			}

			relationTokens := strings.SplitN(relationId, ":", 2)
			if len(relationTokens) != 2 {
				log.Debug("Ignoring unparseable relation id in tag: %v", tagName)
				continue
			}

			relationProperty := relationProperty{}
			relationProperty.UnitId = unitId
			assert.That(key[0] == '_')
			relationProperty.Key = key[1:]
			relationProperty.Value = v
			relationProperty.RelationType = relationTokens[0]
			relationProperty.RelationKey = relationTokens[1]
			relationList = append(relationList, relationProperty)

			continue
		}
	}

	state.Relations = map[string]map[string]string{}
	for _, relation := range relationList {
		relationType := relation.RelationType
		relations, found := state.Relations[relationType]
		if !found {
			relations = map[string]string{}
			state.Relations[relationType] = relations
		}
		relations[relation.Key] = relation.Value
	}

	// TODO: Only if otherwise ready?
	annotationsReady := self.bundleType.IsStarted(state.Relations)

	// For a subordinate charm service (e.g. multimysql), we just watch for the annotation
	if annotationsReady && state.Model.Status == "" && len(status.SubordinateTo) != 0 {
		log.Info("Subordinate instance started (per annotations): %v", self)
		state.Model.Status = "started"
	}

	if !annotationsReady {
		log.Info("Instance not started (per annotations): %v", state.Relations)
		state.Model.Status = "pending"
	}

	log.Info("Status of %v: %v", primaryServiceId, state.Model.Status)

	// TODO: Fetch inherited properties from primary service and merge

	return state, nil
}
Пример #28
0
func (self *Binder) AddDefaultBindingByPointer(p interface{}) {
	t := reflect.TypeOf(p).Elem()
	log.Info("Type is %v", t)
	self.AddDefaultBinding(t)
}
Пример #29
0
func main() {
	rand.Seed(time.Now().UTC().UnixNano())

	options := GetOptions()
	if options == nil {
		log.Fatal("Error reading options")
		os.Exit(1)
	}

	juju.Init()

	binder := inject.NewBinder()

	clientFactory := juju.EnvClientFactory

	if options.AgentConf != "" && options.ApiPasswordPath != "" {
		yaml, err := ioutil.ReadFile(options.AgentConf)
		if err != nil {
			log.Error("Error reading config file: %v", options.AgentConf, err)
			os.Exit(1)
		}

		apiPassword, err := ioutil.ReadFile(options.ApiPasswordPath)
		if err != nil {
			log.Error("Error reading api password file: %v", options.ApiPasswordPath, err)
			os.Exit(1)
		}

		agentConf := map[string]interface{}{}
		err = goyaml.Unmarshal([]byte(yaml), &agentConf)
		if err != nil {
			log.Error("Error reading config file: %v", options.AgentConf, err)
			os.Exit(1)
		}

		clientFactory = func() (*juju.Client, error) {
			//			password := agentConf["apipassword"].(string)
			//			tag := agentConf["tag"].(string)
			//			nonce := agentConf["nonce"].(string)

			password := string(apiPassword)
			tag := "user-admin"
			nonce := ""

			servers := []string{}
			for _, apiaddress := range agentConf["apiaddresses"].([]interface{}) {
				servers = append(servers, apiaddress.(string))
			}

			ca := agentConf["cacert"].(string)
			info := api.Info{
				Addrs:    servers,
				Password: password,
				CACert:   ca,
				Tag:      tag,
				Nonce:    nonce,
			}

			log.Info("%v", log.AsJson(info))

			return juju.SimpleClientFactory(&info)
		}
	}

	binder.AddProvider(clientFactory)

	bundleStore := bundle.NewBundleStore("templates")
	binder.AddSingleton(bundleStore)

	authenticator := options.Authenticator
	binder.AddSingleton(authenticator)
	binder.BindType(reflect.TypeOf((*auth.Authenticator)(nil)).Elem()).ToInstance(authenticator)

	cfTenantIdMap := cf.NewCfTenantIdMap(options.CfTenantId)
	binder.AddSingleton(cfTenantIdMap)

	binder.AddDefaultBindingByPointer((*cf.CfHelper)(nil))

	apiclient, err := clientFactory()

	// TODO: How would we get the full config "from afar"?
	//confParams := map[string]interface{}{}
	////	confParams["name"] = "jxaas"
	////	confParams["firewall-mode"] = "instance"
	////	confParams["development"] = false
	////
	////	confParams["type"] = "ec2"
	////
	////	confParams["ssl-hostname-verification"] = true
	////	confParams["authorized-keys"] = ""
	////
	//	//		"state-port":                DefaultStatePort,
	//	//		"api-port":                  DefaultAPIPort,
	//	//		"syslog-port":               DefaultSyslogPort,
	//	//		"bootstrap-timeout":         DefaultBootstrapSSHTimeout,
	//	//		"bootstrap-retry-delay":     DefaultBootstrapSSHRetryDelay,
	//	//		"bootstrap-addresses-delay": DefaultBootstrapSSHAddressesDelay,
	//	conf, err := config.New(config.NoDefaults, confParams)
	//	if err != nil {
	//		log.Fatal("Error building Juju config", err)
	//		os.Exit(1)
	//	}
	//	apiclient, err := juju.DirectClientFactory(conf)
	if err != nil {
		log.Fatal("Error building Juju client", err)
		os.Exit(1)
	}

	system := core.NewSystem()

	// This sadly doesn't work, because it is very difficult to download a charm :-(
	//	system.AddJxaasCharm(apiclient, "mongo", "cs:~justin-fathomdb/trusty/mongodb")

	{
		bundle, err := bundletype.LoadFromStore(bundleStore, "mongodb")
		if err != nil {
			log.Fatal("Error building mongodb bundle", err)
			os.Exit(1)
		}
		system.AddBundleType(bundle)
	}

	{
		bundle, err := bundletype.LoadFromStore(bundleStore, "mysql")
		if err != nil {
			log.Fatal("Error building mysql bundle", err)
			os.Exit(1)
		}
		system.AddBundleType(bundle)
	}

	{
		bundle, err := bundletype.LoadFromStore(bundleStore, "multimysql")
		if err != nil {
			log.Fatal("Error building multi-mysql bundle", err)
			os.Exit(1)
		}
		system.AddBundleType(bundle)
	}

	{
		bundle, err := bundletype.LoadFromStore(bundleStore, "es")
		if err != nil {
			log.Fatal("Error building elasticsearch bundle", err)
			os.Exit(1)
		}
		system.AddBundleType(bundle)
	}

	{
		bundle, err := bundletype.LoadFromStore(bundleStore, "pg")
		if err != nil {
			log.Fatal("Error building postgres bundle", err)
			os.Exit(1)
		}
		system.AddBundleType(bundle)
	}

	{
		bundle, err := bundletype.NewCassandraBundleType(bundleStore)
		if err != nil {
			log.Fatal("Error building cassandra bundle", err)
			os.Exit(1)
		}
		system.AddBundleType(bundle)
	}

	privateUrl := options.PrivateUrl

	for {
		huddle, err := core.NewHuddle(system, bundleStore, apiclient, privateUrl)
		if err != nil {
			log.Fatal("Error building huddle", err)
			os.Exit(1)
		}
		if isHuddleReady(huddle) {
			log.Info("Huddle config is %v", huddle)
			binder.AddSingleton(huddle)
			break
		}
		time.Sleep(2 * time.Second)
	}

	rest := rs.NewRestServer()
	rest.SetListen(options.ListenAddress)

	typeEndpointXaas := reflect.TypeOf((*endpoints.EndpointXaas)(nil)).Elem()
	binder.AddDefaultBinding(typeEndpointXaas)
	rest.AddEndpoint("/xaas/", typeEndpointXaas)

	typeEndpointXaasPrivate := reflect.TypeOf((*endpoints.EndpointXaasPrivate)(nil)).Elem()
	binder.AddDefaultBinding(typeEndpointXaasPrivate)
	rest.AddEndpoint("/xaasprivate/", typeEndpointXaasPrivate)

	typeEndpointCf := reflect.TypeOf((*cf.EndpointCfRoot)(nil)).Elem()
	binder.AddDefaultBinding(typeEndpointCf)
	rest.AddEndpoint("/cf/", typeEndpointCf)

	injector := binder.CreateInjector()
	rest.WithInjector(injector)

	rest.AddReader(rs.NewJsonMessageBodyReader())
	rest.AddWriter(rs.NewJsonMessageBodyWriter())

	log.Info("Ready!")

	log.Fatal("Error serving HTTP", rest.ListenAndServe())
}