Publicly exposing a WCF restful service via http from Service Fabric - azure-service-fabric

I am trying to expose a WCF based restful service via http and am so far unsuccessful. I'm trying on my local machine first so prove it works. I found a suggestion here that I remove my local cluster and then manually run this powershell command from the SF SDK folder as administrator to recreate it with the machine name binding: .\DevClusterSetup.ps1 -UseMachineName
It created the cluster successfully. I can use the SF Explorer and see in the cluster manifest that entries in the NodeList show the machine name rather than localhost. This seems good.
But the first problem I notice is that if I expand my way through SF Explorer down to the node my app is running on I see an endpoints entry but the URL is not what I'd expect. I am seeing this: http://surfacelap/d5be9425-3247-4290-b77f-1a90f728fb8d/39cda0f7-cef4-4c7f-8af2-d05786a834b0-131111019607641260
Is that what I should see even though I have an endpoint setup? I did not expect the guid and other numbers in the path. This makes me suspect that SF is not seeing my service as being publicly accessible and instead is maybe only setup for internal access within the app? If I dig down into my service manifest I see this as expected:
<Resources>
<Endpoints>
<Endpoint Name="ResolverEndpoint" Protocol="http" Type="Input" Port="80" />
</Endpoints>
</Resources>
But how do I know if the service itself is mapped to it? When I use the crazy long url above and try a simple method of my service I get an http 202 response and no response data as expected. If I then change the method name to one that doesn't exist I get the same thing, not the expected http 404. I've tried using both my machine name and localhost. Same result.
So clearly I'm doing something wrong. Below is my CreateServiceInstanceListeners override. In it you can see I use "ResolverEndpoint" as my endpoint resource name, which matches the service manifest:
protected override IEnumerable<ServiceInstanceListener> CreateServiceInstanceListeners()
{
return new[] { new ServiceInstanceListener((context) =>
new WcfCommunicationListener<IResolverV2>(
serviceContext: context,
wcfServiceObject: new ResolverServiceV2(),
listenerBinding: new WebHttpBinding(WebHttpSecurityMode.None),
endpointResourceName: "ResolverEndpoint"
)
)};
}
What am I doing wrong here?

Here's a way to get it to work: https://github.com/loekd/ServiceFabric.WcfCalc
Essential changes to your code are the use of the public name of your cluster as endpoint URL and an additional WebHttpBehavior behavior on that endpoint.

The endpoint resources specified in the service manifest is shared by all of the replica listeners in the process that use the same endpoint resource name. So if your service has more than one partition it is possible that more than one replica from different partition may end up in the same process. In order to differentiate the messages addressed to different partitions, the listener adds partition ID and additional instance GUID to the path.
If you are going to have a singleton partition service and know that there will not be more than one replicas in the same process you can directly supply EndpointAddress you want to the listener to open at. Use the CodePackageActivationContext API to get the port from the endpoint resource name, node name or IP address from the NodeContext and then provide the path you want the listener to open at.
Here is the code in the WcfCommunicationListener that constructs the Listen Address.
private static Uri GetListenAddress(
ServiceContext serviceContext,
string scheme,
int port)
{
return new Uri(
string.Format(
CultureInfo.InvariantCulture,
"{0}://{1}:{2}/{5}/{3}-{4}",
scheme,
serviceContext.NodeContext.IPAddressOrFQDN,
port,
serviceContext.PartitionId,
serviceContext.ReplicaOrInstanceId,
Guid.NewGuid()));
}
Please note that you can now have only once application, one service and one partition on a node and when you are testing locally, keep the instance count of that service as 1. When deployed in the actual cluster you can use -1 instance count.

Related

RestTemplate annotated by #LoadBalanced get wrong service address by service name from eureka sometimes

I use springcloud to build the system, including many microservices。 For some interface calls, I use resttemplate annotated by #LoadBalance to implement load balancing, and use eureka as a registry center. However, when I call interfaces between different micro services, resttemplate sometimes will connect to wrong service. For example, I have service A, B, C, when service A call a service B's interface, resttemplate annotated by #LoadBalance will find the actual ip&port from eureka by service name first, and then build the actual url and send the request to target server, but sometimes, it will find the service C's ip&port when I call service B's interface, which cause a fail invoking. This case occurs infrequently but nerver disappear, I have been troubled for a long time, could anyone give me some suggestions? Thanks.
I learned why yesterday: it is a bug in spring cloud Dalston.RELEASE(https://github.com/spring-cloud/spring-cloud-commons/issues/224), and we happen to use this version. Spring cloud had fixed this bug in Dalston.SR2, and now it works fine

Communication between microservice using ServiceID from discovery instead of directory host?

I'm new microservice, I'm reading some example about discovery server, I see we can call another microservice api by using url like:
http://inventory-service/api/inventory/{productCode}.
"inventory-service" is a service instance I registered in discovery.
So my question is what is the benefit of using serviceId intead of call directory host:port:
http://localhost:9009/api/inventory/{productCode}.
Let asume you register inventory-service with Eureka server by configuring Eureka serviceUrl in src/main/resources/bootstrap.properties.
spring.application.name=inventory-service
eureka.client.service-url.defaultZone=http://localhost:8761/eureka/
Then build inventory-service and start 2 instances of it by running following commands.
java -jar -Dserver.port=9001 target/inventory-service-0.0.1-SNAPSHOT-exec.jar
java -jar -Dserver.port=9002 target/inventory-service-0.0.1-SNAPSHOT-exec.jar
When you visit Eureka Dashboard http://localhost:8761/ you will see 2 instances of inventory-service registered.
If you want to apply Client Load Balancing from your consumer application you would need a config like this:
server.ribbon.listOfServers=localhost:9001,localhost:9002
server.ribbon.eureka.enabled=false
If you want to start new instances you would need to register them in your consumer configuration.
With ServiceID you don't have to worry about it, because all instances will register with the same identifier. It will be added automatically in the list of available servers.It is one of the advantages of using ServiceId instead hostname

Service Fabric Naming Service not forwarding to endpoint assigned to Guest Executable

I have setup an application with two services, one a standard aspnet core api, and another node express app by following the guide here:
https://learn.microsoft.com/en-us/azure/service-fabric/service-fabric-deploy-existing-app
When I deploy the application locally I can use the naming service to hit the AspNetCore application such as:
http://localhost:19081/sf_node_test_02/AspNetCore/api/values
Likewise, I expect to be able to hit the api of my guest executable using this address:
http://localhost:19081/sf_node_test_02/NodeApp
However, this does not work.
If I use the direct url of the service such as:
http://localhost:30032/ I can see the node js app is in fact working as expected.
Now, I know when running the AspNet core application it is explicitly sending it's listening address back to the naming service but the guest executable is not so that explains why they might behave differently. Also, from what I understand the current version of service fabric does not provide the guest executable the information about a dynamically assigned port so it must be hard coded in the service endpoint and also in the application to listen on the same port.
E.g. If I have:
<Endpoint Name="NodeAppTypeEndpoint" Port="30032" Protocol="http" Type="Input" UriScheme="http"/>
Then in the nodejs app I must also have:
const port = process.env.PORT || 30032;
app.listen(port, () => {
console.log(`Listening on port: ${port}`);
});
Noticed 30032 in both places.
From the documentation:
Furthermore you can ask Service Fabric to publish this endpoint to the
Naming Service so other services can discover the endpoint address to
this service. This enables you to be able to communicate between
services that are guest executables. The published endpoint address is
of the form UriScheme://IPAddressOrFQDN:Port/PathSuffix. UriScheme and
PathSuffix are optional attributes. IPAddressOrFQDN is the IP address
or fully qualified domain name of the node this executable gets placed
on, and it is calculated for you.
I interpreted this to mean that if my ServiceManifest.xml has both UseImplicitHost="true" then it should automatically give the naming service the url constructed by the endpoint description.
http://localhost:19081/sf_node_test_02/NodeApp -> http://localhost:30032
Is it correct that service fabric will automatically give the naming service this listening address for this service?
Is there anyway for me to inspect the mapping in the naming service?
This would let me know if it does have an entry for my node application but it is just different than what I expect or if in fact it has no entry.
If it doesn't have an entry then I don't know how this guest executable application would be visible to the public when deployed in the cloud either.
You can use the QueryManager of FabricClient to list registered endpoints for services in your cluster. This should reveal if there is an endpoint for your node service.
var fabricClient = new FabricClient();
var applicationList = fabricClient.QueryManager.GetApplicationListAsync().GetAwaiter().GetResult();
foreach (var application in applicationList)
{
var serviceList = fabricClient.QueryManager.GetServiceListAsync(application.ApplicationName).GetAwaiter().GetResult();
foreach (var service in serviceList)
{
var partitionListAsync = fabricClient.QueryManager.GetPartitionListAsync(service.ServiceName).GetAwaiter().GetResult();
foreach (var partition in partitionListAsync)
{
var replicas = fabricClient.QueryManager.GetReplicaListAsync(partition.PartitionInformation.Id).GetAwaiter().GetResult();
foreach (var replica in replicas)
{
if (!string.IsNullOrWhiteSpace(replica.ReplicaAddress))
{
var replicaAddress = JObject.Parse(replica.ReplicaAddress);
foreach (var endpoint in replicaAddress["Endpoints"])
{
var endpointAddress = endpoint.First().Value<string>();
Console.WriteLine($"{service.ServiceName} {endpointAddress} {endpointAddress}");
}
}
}
}
}
}

Query on DNS & connect to existing vm

In my current code base, when i create a VM, DNS name is being dynamically set as same as the instance name. For example, consider if my VM name is "anandInstance", DNS name of the name is being generated as "anandInstance.cloudapp.net". Is there a way to change the DNS name like "dns1.cloudapp.net" during the creation thru REST API??
"Connect to existing VM" , is it possible to achieve this option through REST call? In case "connect to existing.." option , we are getting a list of vms/services to choose and VM is getting created successfully. How to achieve the same using API.
Thanks
In my current code base, when i create a VM, DNS name is being
dynamically set as same as the instance name. For example, consider if
my VM name is "anandInstance", DNS name of the name is being generated
as "anandInstance.cloudapp.net". Is there a way to change the DNS name
like "dns1.cloudapp.net" during the creation thru REST API??
I don't think it is possible. Imagine what a nightmare in the portal would become if you were able to do so? How would you link a Cloud Service (whatever.cloudapp.net) to an actual deployment (MyDemoVm123). However you can use your own domain and have CNAME records pointing to your "want-to-change-for-some-reason.cloudapp.net" (frankly I surely think that soon we will use even longer names)
"Connect to existing VM" , is it possible to achieve this option
through REST call?
Connection to a VM is essentially opening a RDP session. If it a windows VM, you can try using the Download RDP file API call. Once you get the file, just start it with "process.start". If it is linux VM, just start SSH client on port 22 (or one you have defined) from the Cloud Service DNS name you have.
UPDATE
From the azure portal,for stand alone machineoption, we are able to give the dns name with deafult cloudoneapp.net. How to do the same
through the rest api call.any specfic paramter is there to specify the
same?
When you are using the REST API, you first create a Cloud Service (still named hosted service in the REST API) where your machine will be hosted. Here you give the name for that hosted service (the dns name with deafult cloudoneapp.net). Then you call the Create Virtual Machine Deployment API action.
In case "connect to existing.." option , we are getting a list of vms/services to choose and VM is getting created successfully. How to
achieve the same using API.
When you want to get list of all VMs, just get a list of all Hosted Services, then get properties of each and make a guess whether it is a VM or a Cloud Service (maybe by querying for Properties of each service). I don't see a direct access to the list of Virtual Machines. But as this feature being PREVIEW, things might change in the future.
Hope my answer is clear?

Cannot start Windows Azure VM programmatically

I'm performing REST API operation Start Role (http://msdn.microsoft.com/en-us/library/jj157189.aspx)
In the link https://management.core.windows.net/{subscription-id}/services/hostedservices/{service-name}/deployments/{deployment-name}/roles/{role-name}/Operations we have replaced {service-name}, {deployment-name} and {role-name} with name of VM.
In result we have next message:
"ResourceNotFoundThe resource service name hostedservices is not supported."
List Hosted Services operation (http://msdn.microsoft.com/en-us/library/windowsazure/ee460781.aspx) shows us that we have 2 WMs as hosted services.
Get Role operaion (http://msdn.microsoft.com/en-us/library/jj157193.aspx) also gives info about each of VMs.
Thanks in advance.
You are using:
{subscription-id}/services/hostedservices/{service-name}/deployments/{deployment-name}/roles/{role-name}/Operations
But the correct Uri is:
{subscriptionID}/services/hostedservices/{serviceName}/deployments/{deploymentName}/roleInstances/{roleInstanceName}/Operations
See the difference?
I haven't worked with this particular operation, however a few things:
service-name: It should be the name of the hosted service (the one with .cloudapp.net) and what you see when you list your hosted service.
deployment-name: Generally speaking it's a GUID returned by Get Deployment operation (http://msdn.microsoft.com/en-us/library/windowsazure/ee460804.aspx).
role-name: Role name is also returned when you do a Get Deployment operation. You should use that. I'm not sure if it is same as the name of your VM.
Can you retry your operation after changing these values?
In my case, deployment name is the name of the first VM I created in this cloud service. So, if I added 3 machines to the same cloud service, all of them have the same deployment name - the name of the first machine.